home *** CD-ROM | disk | FTP | other *** search
Text File | 1990-04-30 | 152.2 KB | 4,252 lines | [TEXT/MPS ] |
- {[j=20/53/1$]}
-
- {-------------------------------------------------------------------------------------------}
- { All of these constants and globals are used only in this unit, and so are defined in the
- IMPLEMENTATION section of our show. This is because if we kept them in the INTERFACE
- section and we changed them, MFracApp and UAreaSelector would get recompiled when they
- didn’t need to. Putting them here prevents that from happening, because MFracApp and
- UAreaSelector are only dependent on the INTERFACE section, not the IMPLEMENTATION. }
- {-------------------------------------------------------------------------------------------}
-
- CONST
-
-
- { Constants used for identification }
-
- kNormalVersion = 4; { Identifies a document created by
- FracApp 2.0 and using the
- TNormalFracAppEngine }
- kFastVersion = 5; { Identifies a document created by
- FracApp 2.0 and using the TFastFracApp
- engine }
- kUnknownVersion = - 1; { Used when initializing the version
- number before the real version is
- known. }
-
- { We define two different palettes for two different situations. We’d like to be able to
- animate when we can; in that case, we attach the palette identified with
- kAnimatingPaletteID to each window we make. If we can’t animate our colors (which occurs
- when we don’t have 32-bit Color QuickDraw), then we fall back on a palette that at least
- guarantees we have the colors we need (kTolerantPalette). }
-
- kTolerantPalette = 1001; { palette ID for not animating }
- kAnimatingPalette = 1002; { palette ID for animating }
- kClut = 501; { ID for the clut resource that we use
- for offscreen blinging. }
-
- { Window and view resource numbers }
-
- kFracAppWindowID = kDefaultWindowID; { ID of the ‘view’ resource used to
- create our windows }
- kAbout = 4000; { ID for our about dialog }
- kAbout2 = 4001; { Ditto }
- kMultiDialog = 2002; { Dialog Id for multipage dialog, gets
- number of pages to do.}
- kHorItem = 3; { item in dialog for EditText of
- horizontal page count. }
- kVerItem = 4; { item in dialog for EditText of vertical
- page count. }
-
- { Constance for our own menu items. }
-
- cNewFromSelection = 1000; { New Fractal from selection. }
- cNewMultiPage = 1001; { New Multipage… }
- cAnimate = 1002; { Start the palettes moving }
- cJumble = 1003; { Mix up the palette }
- cNormal = 1004; { Make the palette normal again }
- cUse32CQD = 1005; { Use 32CQD for offscreen management. }
- cUseHomebrew = 1006; { Use old method for offscreen stuff. }
- cMake72dpi = 1007; { Generate at 72 dpi for the screen. }
- cMake300dpi = 1008; { Generate a 300 dpi image for the
- printer }
- cSlowAlgorithm = 1009; { Use left/right, up/down method }
- cFastAlgorithm = 1010; { Use divide & conquer method }
- cFirstWindowBase = 2000; { Dummy item to find where windows go }
- cContinuingMultiPage = 2001; { Passed to OpenNew when making add’l
- docs }
-
- { The dimensions of our Fractal documents. These are tailored to the size of the spiffy
- color printer we have in DTS. Your values may vary. }
-
- kRectRight = 608;
- kRectBottom = 798;
-
- { TFracAppDocument.LockThePixels takes a Boolean value that determines if we are actually
- locking the pixels, or really unlocking them. These constants define what action to take. }
-
- kLock = TRUE;
- kUnlock = NOT kLock;
-
- { When setting the strings in the infobar (elapsed time, etc.) we need to differentiate
- between setting those strings for the FIRST time, and all subsequent times. When calling
- them for the first time, we pass kForceUpdate. All other times, we pass kDontForceUpdate. }
-
- kForceUpdate = TRUE;
- kDontForceUpdate = NOT kForceUpdate;
-
- { When calling SetPalette, we need to pass a Boolean that tells the Palette manager whether
- or not we want updates when our window is activated. These two constants help clarify what
- we are passing to SetPalette. }
-
- kWantUpdates = TRUE;
- kDontWantUpdates = FALSE;
-
- { String list (STR#) resource number and string indexes. These are used when building up the
- string that describes the algorithm being used. That string is of the form “fractal
- algorithm”/“offscreen routines being used”. }
-
- kAlgorithmStrings = 1200;
- kUsing = 1;
- kUnknownAlgorithm = 2;
- kNormalAlgorithm = 3;
- kFastAlgorithm = 4;
- kSlash = 5;
- kHomebrewRoutines = 6;
- k32CQDRoutines = 7;
-
- { Amount of time to spend on calculating fractals when we are in the foreground and in the
- background. If we can use the Time Manager, then we specify these constants in terms of
- milliseconds, and define a value that is used to convert our units to seconds. If we
- cannot use the Time Manager, then we express our values in ticks, and set our conversion
- value appropriatedly. In either case, we set the background time to as small as possible. }
-
- {$IFC NOT qPerform}
- kFgCalcTime = 50; { 50 msec = 50/1000 = 1/20 of sec }
- kBkCalcTime = 1;
- kUnitsPerSecond = 1000;
- {$ELSEC}
- kFgCalcTime = 3; { 3 ticks = 3/60 = 1/20 of sec }
- kBkCalcTime = 1;
- kUnitsPerSecond = 60;
- {$ENDC}
-
- { Other Thangs... }
-
- kPICTHeaderSize = 512; { 512 bytes off the file are used for our
- info and print info. }
- kNumColors = 195; { number of colors we animate, and use in
- calculation. }
- kNo32BCQD = - 25002; { error code if 32bCQD is not around.
- Used to display a warning that some
- menu items will be disabled. }
- QD32Trap = $AB03; { Trap number of 32 bit QuickDraw (not in
- MPW headers) }
- kAnimationDelay = 2; { Ticks between palette animation }
-
- kMinCCRectSize = 4; { minimum CalcCity rect size }
-
- kWantSeconds = TRUE; { passed to IUTimePString to indicate that
- we want the time string to include seconds. }
-
- {-------------------------------------------------------------------------------------------}
- {$IFC NOT qPerform}
-
- TYPE
-
- { This is the record type we use when installing a time manager task. It includes a field
- for an A5 so that our task can get to our global variables. In addition to a normal time
- manager task record, we define two other fields that are used by the extended “no drift”
- time manager. }
-
- MyTimeTask = RECORD
- A5: LONGINT;
- taskRecord: TMTask;
- tmWakeUp: LONGINT; { In case the extended time manager is
- around }
- tmReserved: LONGINT;
- END;
- {$ENDC}
-
- {-------------------------------------------------------------------------------------------}
-
- VAR
-
- { This is a handle to the color table that we use throughout the program. It is loaded in
- from a ‘clut’ resource early on in the program. It is used when we create an offscreen
- world for the offscreen gDevice’s colortable, and also for some of the whizzy animation
- effects that we do (see MakeOffWorlder, JumblePalette and RestorePalette) }
-
- gOurColors: CTabHandle;
-
- { We have two palettes to attach to windows, depending on whether or not we have 32-Bit
- Color QuickDraw. We figure out which one to use when our application initializes itself,
- and store its resource ID here. Then, whenever we need a palette, we just use the value
- here, rather than refiguring the whole thing out every time. }
-
- gPaletteIDToUse: Integer;
-
- { When using system 6.0.2 or later, we can associate a palette with more than one window. If
- we are running under those conditions, we get the palette we need just once, save it in
- this variable, and attach it to any new windows we create. Otherwise, we just do a
- GetNewPalette every time, and this variable goes unused. }
-
- gPalette: PaletteHandle;
-
- { Just because we HAVE 32-Bit CQD doesn’t mean that we want to use it. This variable is used
- to control whether or not we want to use it if it’s available. It’s initially set to
- gConfiguration.has32BitCQD. }
-
- gUse32BitCQD: Boolean;
-
- { Set to TRUE if we have selected the menu item for the Mariani/Silver algorithm. }
-
- gUseFastAlgorithm: Boolean;
-
- { Normally set to 72. It’s here so that we can implement other resolutions (like 300 dpi) in
- the future. }
-
- gPrintResolution: Integer;
-
- { We set this global variable to TRUE if we want to do palette animation at idle time. We
- also check it when we set up the menus so we know if we need to check the menu item or
- not. }
-
- gAnimate: Boolean;
-
- { This Boolean is set to true when we have jumbled up the palettes. We use it when we are
- setting up the menus so we know if we need to check the menu item or not. }
-
- gPaletteIsJumbled: Boolean;
-
- { We need some sort of clutch when animating. I initially animated whenever my DoIdle method
- was called. However, on faster machines, we could get called more times in a second than
- we care for. So we keep track of when we last animated, and make sure that a decent number
- of ticks go by before animating again. This makes sure that our effects don’t go whizzing
- by too fast on that 100MHz Mac III. }
-
- gLastAnimated: LONGINT;
-
- { We’d like to take advantage to default application palettes if the system supports them.
- (see technote #211). To condition our program in the places necessary to support default
- application palettes, we set this global variable when running on the those systems. }
-
- gHasDefaultApplicationPalettes: Boolean;
-
- { When we are creating a new document, we have some preset values that define the number
- range applied to the Mandelbrot calculations. On the other hand, if we are creating a new
- document based on the selection, we need to initialize the document with that information.
- The following boolean says which method of initialization we are using, and the PageRecord
- holds the necessary information for when gCreateFromPageRecord is TRUE. These are accessed
- in TFracAppDocument.DoInitialState. }
-
- gPageRecord: PageRecord;
-
- { For determining how long it takes to calculate a document, we use the Time Manager. But we
- can only use the Time Manager if we are not using the performance tools, which use the
- VIAs directly and interfere with the Time Manager. Everything would be fine if the the
- performance tools used the Time Manager, but they don’t. At least, not as of MPW 3.2.
- Therefore, we can use the Time Manager only if we don’t use the performance tools, so we
- define this Time Manager task record only under that circumstance. }
-
- {$IFC NOT qPerform}
- gTMTask: MyTimeTask;
- {$ENDC}
-
- { FracApp makes large windows, which tend to hide each other. We have a Windows menu that
- allows easy access to all windows. The way it works is this: all documents are shown in a
- TFracAppWindow. This window’s Show method is overridden to call
- TFracAppApplication.InstallWindowMenuItem when it is shown or hidden, and hence needs to
- be added to or deleted from the Windows menu. InstallWindowMenuItem does what it needs in
- order to remember what windows are visible, and then sets gRebuildWindowsMenu to TRUE.
- Also, when a document’s name changes (say, we just saved it to disk), we need to update
- its name in the menubar, so we again set gRebuildWindowsMenu to TRUE. The next time our
- application’s DoSetupMenus is called, it calls BuildWindowsMenu. BuildWindowsMenu checks
- this flag to see if it needs to do anything. If so, it rebuilds the menu, and clears the
- flag. }
-
- gRebuildWindowsMenu: Boolean;
-
- { The following variables describe the location of the dynamic parts of our Windows menu.
- They are determined by looking for the dummy menu item with the command number
- cFirstWindowBase. That menu item is removed, and any window names replace it. }
-
- gFirstWindowBase: Integer;
- gWindowMenuNumber: Integer;
-
- { We use the International Utilities for creating a time string when we are showing the
- elapsed times. But we can’t just take the standard default format that the IU give us. We
- have to munge them so that we don’t get things like AM or PM after the time, and so that
- we get leading zeros in the places we want them. This handle holds a copy of the default
- INTL(0) or itl0 resource, modified to our liking. }
-
- gIntlHandle: Handle;
-
- { So that we can intelligently move the items in our infobar around, we need to know how
- wide the items are. Finding the width of the labels can be done by doing a StringWidth on
- the text for those labels. In order to find the length of the time items, we create a
- prototype string at application initialization time. This string is created by calling
- IUTimeString with a time of zero. This will represent the width of a time string, as all
- numerical digits are guaranteed to be the same width in all fonts. By creating this
- string, we can use it for determining the spacing for the time elements. }
-
- gMaxWidthTimeString: Str255;
-
- { The next globals are used for the QuickDraw bottlenecks when reading or writing a picture
- to disk. These are needed, since the bottlenecks cannot be owned procedures, and we can’t
- just use local variables. }
-
- gPictSize: LONGINT; { number of bytes used for saving a PICT.
- }
- gPictError: OSErr; { do some error handling in bottleneck. }
- gPictRefNum: Integer; { Need the refnum of the open file too. }
- gPictHandle: PicHandle; { for reading/writing a picture. }
-
- {-------------------------------------------------------------------------------------------}
-
- PROCEDURE TimeCounterThatFetchesItsOwnTaskPtr;
- EXTERNAL;
-
- PROCEDURE TimeCounter;
- EXTERNAL;
-
- PROCEDURE InitCounter(A5: Ptr);
- EXTERNAL;
-
- PROCEDURE InsTimeNoDrift(tmTaskPtr: QElemPtr);
- EXTERNAL;
-
- {-------------------------------------------------------------------------------------------}
- {-----------------------------------Global Procedures---------------------------------------}
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- FUNCTION EqualRGB(RGB1, RGB2: RGBColor): Boolean;
-
- { Compare two RGB records. Return TRUE if their R, G, & B components are the same. }
-
- BEGIN
- WITH RGB1 DO
- EqualRGB := (red = RGB2.red) & (green = RGB2.green) & (blue = RGB2.blue);
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S AOpen}
-
- PROCEDURE MySetPalette(theWindow: TWindow);
-
- { Called whenever we create a window. This routine is an intelligent version of SetPalette.
- It determines if we are running on a system that allows default application palettes or
- not. If not, it gets a new palette, and attaches it to the window. Otherwise, it does
- nothing, allowing the system to attach the correct palette to it. For more information
- on default application palettes, see Technote #211. }
-
- VAR
- palette: PaletteHandle;
- oldPerm: Boolean;
-
- BEGIN
- IF NOT gHasDefaultApplicationPalettes THEN BEGIN
- oldPerm := PermAllocation(TRUE);
- palette := GetNewPalette(gPaletteIDToUse);
- oldPerm := PermAllocation(oldPerm);
- FailNil(palette);
- SetPalette(theWindow.fWMgrWindow, palette, kDontWantUpdates);
- END;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S AFields}
-
- PROCEDURE ShowFracHeader(aTitle: Str255; VAR aRecord: FracRecord; PROCEDURE
- DoToField(fieldName: Str255; fieldAddr: Ptr; fieldType: Integer));
-
- { We have several objects that have a FracRecord somewhere in their instance variables. This
- routine is called in their .Inspect methods to print out the various fields. }
-
- TYPE
- FracRecordPtr = ^FracRecord;
-
- BEGIN
- DoToField(aTitle, NIL, bTitle);
- DoToField(' fType', @aRecord.fType, bOSType);
- DoToField(' hdrId', @aRecord.hdrId, bHexInteger);
- DoToField(' version', @aRecord.version, bInteger);
- DoToField(' done', @aRecord.done, bBoolean);
- DoToField(' elapsed', @aRecord.elapsed, bLongInt);
- DoToField(' startingTime', @aRecord.startingTime, bLongInt);
- DoToField(' endingTime', @aRecord.endingTime, bLongInt);
- DoToField(' areaComplete', @aRecord.areaComplete, bLongInt);
- DoToField(' use32BitCQD', @aRecord.use32BitCQD, bBoolean);
- DoToField(' pages', NIL, bTitle);
- DoToField(' realMin', @aRecord.pages.RealMin, bExtended);
- DoToField(' realMax', @aRecord.pages.RealMax, bExtended);
- DoToField(' imagMin', @aRecord.pages.ImagMin, bExtended);
- DoToField(' imagMax', @aRecord.pages.ImagMax, bExtended);
- DoToField(' multiPaging', @aRecord.pages.multiPaging, bBoolean);
- DoToField(' currentH', @aRecord.pages.currentH, bInteger);
- DoToField(' currentV', @aRecord.pages.currentV, bInteger);
- DoToField(' maxH', @aRecord.pages.maxH, bInteger);
- DoToField(' maxV', @aRecord.pages.maxH, bInteger);
- DoToField(' deltaP', @aRecord.deltaP, bExtended);
- DoToField(' deltaQ', @aRecord.deltaQ, bExtended);
- DoToField(' plotWidth', @aRecord.plotWidth, bLongInt);
- DoToField(' plotHeight', @aRecord.plotHeight, bLongInt);
- DoToField(' calcRect', @aRecord.calcRect, bRect);
- END;
-
-
- {-------------------------------------------------------------------------------------------}
- {$S ARea}
-
- PROCEDURE StripString(VAR aString: Str255);
-
- { Utility routine that strips leading and trailing spaces. This is used by the time printing
- routines to remove mornStr and eveStr, wherever they may be. }
-
- VAR
- i: integer;
-
- BEGIN
- i := LENGTH(aString);
- REPEAT
- IF aString[i] = ' ' THEN
- i := i - 1;
- UNTIL (aString[i] <> ' ');
- aString[0] := char(i);
-
- i := 1;
- REPEAT
- IF aString[i] = ' ' THEN
- i := i + 1;
- UNTIL (aString[i] <> ' ');
- IF i > 1 THEN
- Delete(aString, 1, i - 1);
- END;
-
- {-------------------------------------------------------------------------------------------}
- {------------------------------TFracAppApplication Methods----------------------------------}
- {-------------------------------------------------------------------------------------------}
- {$S AInit}
-
- PROCEDURE TFracAppApplication.IFracAppApplication(itsMainFileType: OSType);
-
- { Initializes the application and globals. Called by the main application when the
- applicaton object is created. }
-
- CONST
- kZeroTime = 0;
-
- VAR
- palette: PaletteHandle;
- i: Integer;
-
- BEGIN
- fWindowList := NIL;
- SELF.IApplication(itsMainFileType);
-
- fWindowList := NewList;
-
- { Collect some information needed for working with the time string items in the
- infobar. In order to convert an elapsed time, in seconds, into a time string,
- we use IUTimePString. We pass to it a modified Intl0 Handle, that has no
- morning and evening string attribute, and will display the time starting at
- 00:00:00, rather than 12:00:00 or 24:00:00. This modified Intl0 Handle is
- saved in “gIntlHandle” }
-
- gIntlHandle := IUGetINTL(0);
- FailNil(gIntlHandle);
- FailOSErr(HandToHand(Handle(gIntlHandle)));
-
- WITH Intl0Hndl(gIntlHandle)^^ DO BEGIN
- timeCycle := zeroCycle;
- mornStr := ' ';
- eveStr := ' ';
- END;
-
- IUTimePString(kZeroTime, kWantSeconds, gMaxWidthTimeString, gIntlHandle);
- StripString(gMaxWidthTimeString);
-
- { Set gRebuildWindowsMenu to TRUE to force the Windows menu to be rebuilt for
- the first time. This is important, so that our dummy menu item gets removed.
- Also, call CmdToMenuItem to find the menu item of our first window entry in
- the menu. }
-
- gRebuildWindowsMenu := TRUE;
- CmdToMenuItem(cFirstWindowBase, gWindowMenuNumber, gFirstWindowBase);
-
- { If 32-bit Color QuickDraw is around, we make a note that we could like to use
- it for our offscreen routines as a default. However, this can be turned off
- if we’d like to offscreen things the old fashioned way with a flick of a menu
- item. Also, decide which palette we are going to use for our windows,
- depending on if we have 32-bit Color QuickDraw. If 32-bit Color QuickDraw is
- not around, then show an alert that tells the user that some features will
- not be available. }
-
- gUse32BitCQD := gConfiguration.has32BitQD;
-
- IF gConfiguration.has32BitQD THEN BEGIN
- gPaletteIDToUse := kAnimatingPalette;
- END
- ELSE BEGIN
- gPaletteIDToUse := kTolerantPalette;
- StdAlert(kNo32BCQD);
- END;
-
- { We’d like to take advantage to default application palettes if the system
- supports them (see technote #211). To condition our program in the places
- necessary to support default application palettes, we set this global
- variable when running on the appropriate systems. }
-
- gHasDefaultApplicationPalettes := (gConfiguration.systemVersion >= $0602);
-
- { We can calculate our fractals in one of two different ways. There is a
- pixel-by-pixel method, which is good for complicated fractals, and there is
- another method that incurs some additional overhead, but more than makes this
- up in optimizations on documents that have some large areas that are all the
- same color. By default, we use the latter, faster method. }
-
- gUseFastAlgorithm := TRUE;
-
- { Set the default print resolution to 72 dpi. This in anticipation of the day
- when this program will be flexible enough to calculate at other resolutions.
- However, for now, it’s unsupported. }
-
- gPrintResolution := 72;
-
- { Start off not animating, initialize the palettes to normal (not jumbled) and
- set the last time that we did animate to sometime way in the past (Jan 1,
- 1904) }
-
- gPaletteIsJumbled := FALSE;
- gAnimate := FALSE;
- gLastAnimated := 0;
-
- { If we are not using the Performance tools, we can use the Time Manager for
- apportioning our time, and for calculating the elapsed time to calculate a
- fractal. If so, install a time manager task. If we are running under
- 6.0.3 or later, we can take advantage of the fact that the Time Manager
- gives our time task procedure a pointer to our task record. Otherwise, we
- have to fall back on method of remembering a reference to our global by hand.
- See Technote #180, sub-section “Time Manager Tasks”, for more information on
- this.
-
- Also note that when I install the time manager task, that I call my own glue
- routine rather than just calling InsTime. This is because I have some special
- Assembly language glue that calls InsXTime. This is a new time manager call
- that offers a “no-drift” feature. In other words, the difference between
- successive calls to my time manager procedure is exactly what I specified,
- rather than what I specified, plus the overhead involved in the time manager,
- plus the time it takes my time task procedure to execute. InsTimeNoDrift
- works in such a way that it will automatically take advantage of this feature
- if it is there. }
-
- {$IFC NOT qPerform}
- gTMTask.A5 := GetA5;
- IF gConfiguration.systemVersion < $0603 THEN BEGIN
- gTMTask.taskRecord.tmAddr := @TimeCounterThatFetchesItsOwnTaskPtr;
- InitCounter(@gTMTask.taskRecord);
- END
- ELSE BEGIN
- gTMTask.taskRecord.tmAddr := @TimeCounter;
- END;
-
- gCounter := 0;
- InsTimeNoDrift(@gTMTask.taskRecord);
- PrimeTime(@gTMTask.taskRecord, 1);
- {$ENDC}
-
- IF gDeadStripSuppression THEN BEGIN
- IF Member(TObject(NIL), TFracAppWindow) THEN;
- IF Member(TObject(NIL), TFracAppView) THEN;
- IF Member(TObject(NIL), TNoFlashStaticText) THEN;
- END;
-
- SELF.CreateManyColors;
-
- { Associate one of our palettes with the clipboard window. This is so that we
- can see our fractal properly if we copy them to the desk scrap. }
-
- MySetPalette(gClipWindow);
-
- END; { TFracAppApplication.IFracAppApplication
- }
-
- {-------------------------------------------------------------------------------------------}
- {$S AWriteFile}
-
- PROCEDURE TFracAppApplication.AutoSaveThisGuy(doneDoc: TFracAppDocument);
-
- { This is just a simple routine that gets called when we are done calculating a fractal
- document that is part of a multipaging process. It creates a file name based on what
- part of the overall fractal it represents, and saves everything out to that file. It then
- saves some information so that we can know where to create any subsequent documents, and
- closes the document. }
-
- CONST
- kDontAskForFileName = FALSE;
- kNotMakingCopy = FALSE;
-
- VAR
- Hpage, Vpage: Str255;
-
- BEGIN
-
- { First, save off and close the previous document. We title the thing “Set- x,y”, and
- tell MacApp to jam it to whatever directory we happen to be set to. }
-
- NumToString(gPageRecord.currentH, Hpage);
- NumToString(gPageRecord.currentV, Vpage);
- doneDoc.SetTitle(ConCat('Set- ', Hpage, ',', Vpage));
- {$Push} {$H-} { Pascal will complain about fVolRefNum
- otherwise. }
- FailOSErr(GetVol(NIL, doneDoc.fVolRefNum));
- {$Pop}
-
- { Before we close the document, we have to leave some information laying around
- so we know how to create the next one. All of this information is
- conveniently kept in the ‘pages’ field of the FracHeader. So we just save it
- in a global PageRecord variable. }
-
- gPageRecord := doneDoc.GetFracHeader.pages;
-
- { Save the document with no questions asked, using the name of the document
- that was set before. If we have a problem with the file, an alert will come
- up freezing the operation for user input, but that is OK. We at least try to
- be automatic, but if something freaks out it is OK to pause. }
-
- doneDoc.Save(cSave, kDontAskForFileName, kNotMakingCopy);
- doneDoc.Close;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppApplication.BuildWindowsMenu;
-
- { We have a special kind of window that notifies our application when it opens or closes
- itself. Our application then either adds or removes that window from a list it keeps.
- Also, if a window renames itself (say, the owning document was saved), it notifies the
- application. In both cases, this method gets called to totally rebuild the Windows menu.
- It also matches up the top window with a menu item, and puts a check mark by it. }
-
- VAR
- windowsMenu: MenuHandle;
- curItem: Integer;
- nItems: Integer;
-
- PROCEDURE AppendWindowToMenu(theWindow: TObject);
-
- { This sub-procedure is responsible for adding the name of a window to the
- menu. It is called by the TList.Each method for each item in the list. We
- coerce the object to a TWindow, get the window’s title, and append it to the
- window’s menu. Note how we do this: we first add an item with a dummy name,
- and then rename it with SetItem. This is to avoid the Menu Manager’s
- interpreting things like “/,” “<,” “!,” and other meta characters. }
-
- VAR
- itsTitle: Str255;
-
- BEGIN
- TWindow(theWindow).GetTitle(itsTitle);
- AppendMenu(windowsMenu, 'x');
- SetItem(windowsMenu, curItem, itsTitle);
- curItem := curItem + 1;
- END;
-
- BEGIN
- IF gRebuildWindowsMenu THEN BEGIN
-
- windowsMenu := GetMHandle(gWindowMenuNumber);
- nItems := CountMItems(windowsMenu);
-
- IF nItems >= gFirstWindowBase THEN BEGIN
- FOR curItem := nItems DOWNTO gFirstWindowBase DO BEGIN
-
- DelMenuItem(windowsMenu, curItem);
-
- { The Menu Manager has this feature (yes, it’s really a feature), where it sets
- the top bit of enableFlags when you call DelMenuItem. It does this so that
- any items that are being scrolled into the enableFlags range will
- automatically be enabled. This is being consistant with the fact that all
- menu items beyond 32 are enabled. Anyway, it messes us up BuildWindowsMenu,
- because we call it in the middle of DoSetupMenus. If we delete all menu items
- here, we would like to have the Windows menu title greyed out. However, that
- won’t happen, because DelMenuItem set some bits in enableFlags, and MacApp
- won’t gray out the menu title. So we compensate for that by disabling the
- 32nd menu item by hand. }
-
- DisableItem(windowsMenu, 31);
-
- END;
- END;
-
- curItem := gFirstWindowBase;
- fWindowList.Each(AppendWindowToMenu);
- gRebuildWindowsMenu := FALSE;
-
- END;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ATerminate}
-
- PROCEDURE TFracAppApplication.Close; OVERRIDE;
-
- { The Close method to allow us to clean up before the application quits. We override this
- method rather than TFracAppApplication.Free, because .Free rarely gets called. The
- only time it can get called is when TApplication.Run returns to the main program. At that
- point, it is the main procedure’s responsibility to call .Free. However, hardly anyone
- ever does that, so we override TFracAppApplication.Close instead, which gets called when
- the user selects Quit.
-
- Our Quit item just removes the time manager task. Failure to do so will leave a pointer to
- a no-longer existant task record and task procedure. Once another application is launched,
- these will get overridden in memory, and the time manager will crash your machine with
- glee. }
-
- BEGIN
- INHERITED Close;
- {$IFC NOT qPerform}
- RmvTime(@gTMTask.taskRecord);
- {$ENDC}
- END; { TFracAppApplication.Close }
-
- {-------------------------------------------------------------------------------------------}
- {$S AInit}
-
- PROCEDURE TFracAppApplication.CreateManyColors;
-
- { Called during the initialization of our application to set up some color stuff. First of
- all, if we can take advantage of the default application palette, we get the palette,
- and set set it as the default. Note that it’s documented in technote #211 that we can just
- define a ‘pltt’ resource of ID 0, and that will automatically be used as the default
- application palette. However, through the wonders of System Software Engineering, that no
- longer works on a Mac IIci and System 6.0.4. Explicitly setting the application palette is
- the workaround. But I digress...After we set the palette, we read in and save a reference
- to the color table used for our offscreen devices and palette manager animation stuff. }
-
- VAR
- oldPerm: Boolean;
-
- BEGIN
-
- { We can take advantage of default application palettes under System 6.0.2 or
- later. So check for this before trying to set such a palette. If we don’t
- support application palettes, MySetPalette will kick in when we create a
- window and attach an individual palette to each window. }
-
- IF gHasDefaultApplicationPalettes THEN BEGIN
- oldPerm := PermAllocation(TRUE);
- gPalette := GetNewPalette(gPaletteIDToUse);
- oldPerm := PermAllocation(oldPerm);
- FailNil(gPalette);
- SetPalette(WindowPtr( - 1), gPalette, kDontWantUpdates);
- END;
-
- { Now allocate a color table that we will use whenever we create a new
- document. This is so we have the same color table for each document. }
-
- oldPerm := PermAllocation(TRUE);
- gOurColors := GetCTable(kClut);
- oldPerm := PermAllocation(oldPerm);
- FailNilResource(gOurColors);
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- FUNCTION TFracAppApplication.DoIdle(phase: IdlePhase): Boolean; OVERRIDE;
-
- { Performs Idle time processing for the application. This will do the fractal calculation
- during idle time. It will allow each open document a chance to calculate. The way we
- do this is in a front to back order. We get a reference to the top document. If it is not
- done, we call its engine to calculate a little bit of the fractal, and then get out
- of here. If the top document IS done, we find the next document behind it, and give
- it some time, if needed. This process is carried all the way back for as many documents as
- we have.
-
- This routine is also responsible for doing the Palette Manager animation, if the user has
- selected that option. Finally, if a document signals that it’s done, and that document is
- part of a multipage set, we save that document to disk and start a new one. }
-
- VAR
- documentToCalculate: TFracAppDocument;
- documentToAnimate: TFracAppDocument;
- theDocumentFinishedCalculating: Boolean;
-
- FUNCTION EachWMgrWindowDoTil(FUNCTION DoToWMgrWindow(theWMgrWindow: WindowPtr): Boolean):
- WindowPtr;
-
- { Iterate over the windows on the screen, looking for one that satisfies a
- certain condition. This condition is defined by the procedure passed in to
- us. We send to the window pointer, and it returns either yea or nay. If nay,
- then we get another window. If yea, then we return the window pointer to the
- caller. If no windows get a yea, then we return NIL. }
-
- VAR
- aWindowPtr: WindowPtr;
- done: Boolean;
-
- BEGIN
- EachWMgrWindowDoTil := NIL;
- aWindowPtr := GetWindowList;
- done := FALSE;
- WHILE (aWindowPtr <> NIL) & NOT done DO BEGIN
- done := DoToWMgrWindow(aWindowPtr);
- IF done THEN
- EachWMgrWindowDoTil := aWindowPtr
- ELSE
- aWindowPtr := WindowPtr(WindowPeek(aWindowPtr)^.nextWindow);
- END;
- END;
-
- PROCEDURE GetDocumentToCalculate(VAR docToCalculate: TFracAppDocument);
-
- { Looks for a document that needs calculation time. It does this by calling
- EachWMgrWindowDoTil with a procedure that looks at the document attached to
- that window, and asks if it needs calculation time. If it does, then we
- return that document to the caller. If we don’t find one, we return NIL. }
-
- VAR
- windowToCalculate: WindowPtr;
- theTWindow: TWindow;
-
- FUNCTION FoundFirstUnfinishedTFracAppWindow(theWMgrWindow: WindowPtr): Boolean;
-
- BEGIN
- theTWindow := WMgrToWindow(theWMgrWindow);
- FoundFirstUnfinishedTFracAppWindow := (theTWindow <> NIL) &
- Member(theTWindow, TFracAppWindow) &
- NOT TFracAppDocument(theTWindow.
- fDocument).GetDone;
- END;
-
- BEGIN
- windowToCalculate := EachWMgrWindowDoTil(FoundFirstUnfinishedTFracAppWindow);
- IF windowToCalculate <> NIL THEN
- docToCalculate := TFracAppDocument(theTWindow.fDocument)
- ELSE
- docToCalculate := NIL;
- END;
-
- PROCEDURE GetDocumentToAnimate(VAR docToAnimate: TFracAppDocument);
-
- { Finds a document that will do some animation for us. This routine calls
- EachWMgrWindowDoTil to find the first TFracAppWindow it can, and returns the
- attached document. If there are no such windows, then return NIL. }
-
- VAR
- windowToAnimate: WindowPtr;
- theTWindow: TWindow;
-
- FUNCTION FoundTopTFracAppWindow(theWMgrWindow: WindowPtr): Boolean;
-
- BEGIN
- theTWindow := WMgrToWindow(theWMgrWindow);
- FoundTopTFracAppWindow := (theTWindow <> NIL) & Member(theTWindow,
- TFracAppWindow);
- END;
-
- BEGIN
- windowToAnimate := EachWMgrWindowDoTil(FoundTopTFracAppWindow);
- IF windowToAnimate <> NIL THEN
- docToAnimate := TFracAppDocument(theTWindow.fDocument)
- ELSE
- docToAnimate := NIL;
- END;
-
- BEGIN
-
- GetDocumentToCalculate(documentToCalculate);
- GetDocumentToAnimate(documentToAnimate);
-
- IF documentToAnimate <> NIL THEN
- documentToAnimate.AnimateColors;
-
- IF documentToCalculate <> NIL THEN
- theDocumentFinishedCalculating := documentToCalculate.CalcTown;
-
- IF (documentToCalculate = NIL) & (documentToAnimate = NIL) THEN
- SELF.SetIdleFreq(kMaxIdleTime);
-
- { See if we are doing multipage operation, and if so, save the document, close
- the window and start a new one. }
-
- IF (documentToCalculate <> NIL) & theDocumentFinishedCalculating &
- (documentToCalculate.IsMultiPaging) THEN BEGIN
- SELF.AutoSaveThisGuy(documentToCalculate);
- SELF.MakeNewGibbleyFromGPageRecord;
- END;
-
- { Idle Time handler have to return a boolean value that states whether or not
- they have removed themselves from memory. This was something that was
- supported transparently in MacApp 1.x, but things have changed since then.
- Now that MacApp 2.0 is MultiFinder aware, it tries to be more intelligent
- about giving time up to background apps. Because of this, it manages the time
- given to idle time handlers more thoroughly. It maintains fields that say how
- often they should be called, and when the last time one was called. Normally,
- it tries to set this latter field when the TEvtHandler.DoIdle method is done.
- However, what happens if the object removed itself from the chain and freed
- itself from memory? The reference to the object is no longer valid, and
- MacApp would be writing to random memory. To prevent this from happening, we
- tell MacApp if we are still around. If we return TRUE, MacApp knows that we
- are history. }
-
- DoIdle := FALSE;
-
- END; { TFracAppApplication.DoIdle }
-
- {-------------------------------------------------------------------------------------------}
- {$S AOpen}
-
- FUNCTION TFracAppApplication.DoMakeDocument(itsCmdNumber: CmdNumber): TDocument; OVERRIDE;
-
- { Launches a TFracAppDocument; called when application’s icon is opened, or when New, New
- From Selection, New MultiPage, or Open is requested by the user. After it is done, the
- view and window can be created, relying upon the data in the document. Every application
- which uses documents MUST override this method }
-
- VAR
- aFracAppDocument: TFracAppDocument;
-
- BEGIN
-
- { Allocate and initialize the document. }
-
- New(aFracAppDocument);
- FailNil(aFracAppDocument);
-
- { Now initialize the document fields, and set up the global state of the
- fractal to a default set of the starting fractal. We specify here what kind
- of TOffscreen object to create in IFracAppDocument. In order to use
- TNewCoolOffscreen (ie, the new 32-bit CQD stuff), we both have to have 32-bit
- CQD and want to use it. This could be specified by a menu item, for example. }
-
- aFracAppDocument.IFracAppDocument(gConfiguration.has32BitQD & gUse32BitCQD,
- itsCmdNumber);
-
- { We successfully created a document so we can return the document object for
- use by the application. }
-
- DoMakeDocument := aFracAppDocument;
-
- { Set the idle time so that we start calculating now! }
-
- SELF.SetIdleFreq(0);
- END; { TFracAppApplication.DoMakeDocument }
-
- {-------------------------------------------------------------------------------------------}
- {$S ADoCommand}
-
- FUNCTION TFracAppApplication.DoMenuCommand(aCmdNumber: CmdNumber): TCommand; OVERRIDE;
-
- { Handles the menu selections that have global thermonuclear impact. In this case, we handle
- the menu items that determine the characteristics of the next fractal document we create
- (dpi settings, calculation method, or use of 32 Bit CQD), and the Windows menu. Note that
- we have to do some special handling of the Windows menu, as MacApp knows nothing about it;
- we created that puppy ourselves. }
-
- BEGIN
- DoMenuCommand := gNoChanges;
-
- { Check to see if the user selected one of the window names that is dynamically
- added to the Windows menu. It does this in two ways. First, it checks to see
- if this is a menu command number that MacApp doesn’t know about. In other
- words, is this a menu that didn’t start off life in a ‘cmnu’ resource? If
- not, then we added this menu item by hand with Menu Manager calls, and it
- could be a Windows menu item. We also need to check to see if the command
- number passed to us is the same as the dummy menu item we installed to
- determine the location of the window menu items. This is because the first
- window menu item will replace that dummy item, and assume its command number.
- If either of these cases is true, then call DoWindowsMenu to deal with
- bringing the window forward. }
-
- IF (aCmdNumber < 0) | (aCmdNumber = cFirstWindowBase) THEN BEGIN
-
- SELF.DoWindowsCommand(aCmdNumber)
-
- END
- ELSE BEGIN
-
- { A menu item that MacApp knows about was selected. }
-
- CASE aCmdNumber OF
-
- { Determine whether or not to use the 32 Bit CQD routines for our offscreen
- creation and management. If not, we’ll use some home grown routines. }
-
- cUse32CQD: BEGIN
- gUse32BitCQD := TRUE;
- END;
- cUseHomebrew: BEGIN
- gUse32BitCQD := FALSE;
- END;
-
- { Set the printing/calculation resolution. This would be handy for generating
- some 300 dpi pictures for laserprinters and such. }
-
- cMake72dpi: BEGIN
- gPrintResolution := 72;
- END;
- cMake300dpi: BEGIN
- gPrintResolution := 300;
- END;
-
- { Which algorithm to use. Either calculate everything a pixel at a time, or use
- the Mariani/Silver algorithm to preflight certain solid areas. }
-
- cSlowAlgorithm: BEGIN
- gUseFastAlgorithm := FALSE;
- END;
- cFastAlgorithm: BEGIN
- gUseFastAlgorithm := TRUE;
- END;
-
- { E. None of the above. }
-
- OTHERWISE DoMenuCommand := INHERITED DoMenuCommand(aCmdNumber);
- END;
- END;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppApplication.DoSetupMenus; OVERRIDE;
-
- { Determines the state of our menus. Before MacApp calls DoSetupMenus, it goes through and
- disables all the menu items, and removes any check marks. Then, starting with gTarget, it
- calls DoSetupMenus. Each DoSetupMenus should call its INHERITED method first so that they
- can set up the world the way the like it, and then we have a chance to override that
- (there’s that word again...). In this case, TFracAppApplication deals with all menu items
- that have a global aspect (like the options for creating a new document), but aren’t
- handled by TApplication. }
-
- VAR
- windowsMenu: MenuHandle;
- i: Integer;
- topTWindow: TWindow;
- windowNumber: Integer;
-
- BEGIN
-
- INHERITED DoSetupMenus; { Do mainline stuff first. }
-
- EnableCheck(cUse32CQD, gConfiguration.has32BitQD, gConfiguration.has32BitQD &
- gUse32BitCQD);
- EnableCheck(cUseHomebrew, TRUE, NOT gUse32BitCQD);
- EnableCheck(cMake72dpi, FALSE, (gPrintResolution = 72));
- EnableCheck(cMake300dpi, FALSE, (gPrintResolution = 300));
- EnableCheck(cSlowAlgorithm, TRUE, NOT gUseFastAlgorithm);
- EnableCheck(cFastAlgorithm, TRUE, gUseFastAlgorithm);
-
- { Make sure that our Windows menu is up to date. BuildWindowsMenu is our own
- routine the will see if the Windows menu needs updating. If so, it will tear
- it down, and rebuild it with the current list of windows. }
-
- SELF.BuildWindowsMenu;
-
- { Enable all of the items in the Window Menu. Also, match up the top window
- with its menu item, and put a check mark by the menu item. }
-
- windowsMenu := GetMHandle(gWindowMenuNumber);
- IF NOT fWindowList.IsEmpty THEN BEGIN
- FOR i := gFirstWindowBase TO (gFirstWindowBase+fWindowList.GetSize-1) DO BEGIN
- EnableItem(windowsMenu, i);
- END;
- END;
-
- topTWindow := SELF.WMgrToWindow(FrontWindow);
- IF topTWindow <> NIL THEN BEGIN
- windowNumber := fWindowList.GetSameItemNo(topTWindow);
- IF windowNumber > 0 THEN BEGIN
- CheckItem(windowsMenu, gFirstWindowBase + windowNumber - 1, TRUE);
- END;
- END;
-
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppApplication.DoShowAboutApp; OVERRIDE;
-
- { Show our own About box rather than MacApp’s fine but rather standard one. We simply use a
- MacApp view and window, and call PoseModally on its TDialogView. }
-
- VAR
- theWindow: TWindow;
- theWindow2: TWindow;
- dismisser: IDType;
- dismisser2: IDType;
-
- BEGIN
- theWindow := TWindow(NewTemplateWindow(kAbout, NIL));
- REPEAT
- dismisser := TDialogView(theWindow.FindSubView('DLOG')).PoseModally;
- IF dismisser = 'icon' THEN BEGIN
- theWindow2 := TWindow(NewTemplateWindow(kAbout2, NIL));
- dismisser2 := TDialogView(theWindow2.FindSubView('DLOG')).PoseModally;
- theWindow2.Close;
- END;
- UNTIL dismisser = 'okok';
- theWindow.Close;
- END; { TFracAppApplication.DoShowAboutApp }
-
- {-------------------------------------------------------------------------------------------}
- {$S ADoCommand}
-
- PROCEDURE TFracAppApplication.DoWindowsCommand(aCmdNumber: CmdNumber);
-
- { Sub-method to handle the Windows menu. Called by TFracAppApplication.DoMenuCommand to map
- the menu item selected to a window, and to bring that window to front. It does this by
- converting the menu item number into an index into the application’s fWindowList, and
- extracting that TWindow. When we get it, we bring it to front with the TWindow.Select
- method. }
-
- VAR
- menu, item: Integer;
- chosenWindow: TWindow;
-
- BEGIN
- CmdToMenuItem(aCmdNumber, menu, item);
- chosenWindow := TWindow(fWindowList.At(item - gFirstWindowBase + 1));
- IF qDebug THEN
- FailNil(chosenWindow);
- chosenWindow.Select;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppApplication.InstallWindowMenuItem(window: TWindow; install: Boolean);
-
- { This method is called by TFracAppWindows when they either show or hide themselves. They
- pass in a reference to themselves (SELF), and whether they are showing or hiding
- themselves. This routine then either adds that window to or deletes it from a list it
- maintains for the purpose. After it’s done that, it flags the menus as dirty. }
-
- BEGIN
- IF install THEN
- fWindowList.InsertLast(window)
- ELSE
- fWindowList.AtDelete(fWindowList.GetSameItemNo(window));
-
- InvalidateMenus;
- gRebuildWindowsMenu := TRUE;
- END; { InstallWindowMenuItem }
-
- {-------------------------------------------------------------------------------------------}
- {$S AOpen}
-
- PROCEDURE TFracAppApplication.MakeNewGibbleyFromGPageRecord;
-
- { Handy routine used when multi-paging. After the previous document has been closed, we want
- to make a new one. We figure out the coordinates of the next page to be done, and check to
- see if it is in range of our maximum page range. If not, we are all done, and don’t do
- anything. However, if we need to continue, we call OpenNew to kick off a new document. }
-
- VAR
- morePagesToMake: Boolean;
- deltaReal, deltaImag: Extended; { rectangle size when making new multiple
- page doc. }
-
- BEGIN
-
- { Work on creating the new document. We base this on the values of RealMin,
- ImagMin, RealMax, and ImagMax that we save from the last document. These
- values are jammed into gPageRecord before the old document is erased from
- existence. }
-
- deltaReal := gPageRecord.RealMax - gPageRecord.RealMin;
- deltaImag := gPageRecord.ImagMax - gPageRecord.ImagMin;
-
- morePagesToMake := TRUE;
-
- WITH gPageRecord DO BEGIN
- IF currentH < maxH THEN BEGIN
- currentH := currentH + 1; { We just move one page to the right. }
-
- ImagMin := ImagMax;
- ImagMax := ImagMin + deltaImag;
- END
- ELSE BEGIN
-
- { If we are off the end of the maximum number of pages vertically,
- we are done, so we can just mark it as done and skip it. }
-
- IF currentV < maxV THEN BEGIN
- currentH := 1; { Move back to the left hand side. }
- currentV := currentV + 1; { and down a page. }
-
- ImagMin := ImagMin - ((maxH - 1) * deltaImag);
- ImagMax := ImagMin + deltaImag;
-
- { Move down a page vertically, based on old position. }
- RealMin := RealMax;
- RealMax := RealMin + deltaReal;
- END
- ELSE BEGIN
- morePagesToMake := FALSE;
- END; { if done with page }
- END; { if done with this row }
- END; { with gPageRecord }
-
- IF morePagesToMake THEN BEGIN
- SELF.OpenNew(cContinuingMultiPage);
- END;
- END; { MakeNewGibbleyFromGPageRecord }
-
- {-------------------------------------------------------------------------------------------}
- {$S AFields}
-
- PROCEDURE TFracAppApplication.Fields(PROCEDURE DoToField(fieldName: Str255; fieldAddr: Ptr;
- fieldType: Integer)); OVERRIDE;
-
- BEGIN
- DoToField('TFracAppApplication', NIL, bClass);
- DoToField('fWindowList', @fWindowList, bObject);
-
- { Print fields of anscestors }
- INHERITED Fields(DoToField);
- END; { TFracAppApplication.Fields }
-
- {-------------------------------------------------------------------------------------------}
- {---------------------------------TPICTDocument Methods-------------------------------------}
- {-------------------------------------------------------------------------------------------}
- {$S AOpen}
-
- PROCEDURE TPICTDocument.IPICTDocument;
-
- { Init for the TPICTDocument. We don’t have any variables to initialize, so just set up the
- TDocument object itself. }
-
- BEGIN
- SELF.IDocument(kFileType, kSignature, kUsesDataFork, NOT kUsesRsrcFork,
- NOT kDataOpen, NOT kRsrcOpen);
- END; { TPICTDocument.IPICTDocument }
-
- {-------------------------------------------------------------------------------------------}
- {$S AWriteFile}
-
- PROCEDURE TPICTDocument.DoDrawPicture;
-
- { Abstract method here. Because our TPICTDocument supports writing to the disk, we need some
- way to generate the drawing commands. However, the generic TPICTDocument knows nothing
- about how the data is stored or should be imaged. Therefore, we create the name of a
- method that should be called, and require that this method be overridden in order for it
- to have any effect. If this method is not overridden, the programmer gets a message in the
- debugger saying so. }
-
- BEGIN
- IF qDebug THEN
- ProgramBreak('TPICTDocument.DoDrawPicture: Gotta override me!');
- END; { TPICTDocument.DoDrawPicture }
-
- {-------------------------------------------------------------------------------------------}
- {$S AWriteFile}
-
- PROCEDURE PictSizer(dPointer: Ptr; nextHunk: Integer);
-
- { This routine will size the current image as it goes to the disk. It won’t actually save
- any data or anything, but will merely watch the bytes go by keeping track of how many go
- by. The size is used by DoNeedDiskSpace. }
-
- BEGIN
- gPictSize := gPictSize + nextHunk;
- END; { PictSizer }
-
- {-------------------------------------------------------------------------------------------}
- {$S AWriteFile}
-
- PROCEDURE TPICTDocument.DoNeedDiskSpace(VAR dataForkBytes, rsrcForkBytes: LONGINT); OVERRIDE;
-
- { Routine to find out how much disk space will be required to save the data. This does not
- call the Inherited DoNeedDiskSpace since we don’t support printing info here. The routine
- will replace the PutPicProc of the port with our PictSizer routine. When the picture is
- created here, no bytes will actually be allocated or saved, we will just watch it go by
- and save off the size in the global variable. That value is returned as the expected
- document size, along with the size of the PICT user data area of 512 bytes. }
-
- VAR
- newProcs: CQDProcs;
- oldProcs: QDProcsPtr; { bug in include files, CGrafPort has
- QDProcs *** }
-
- BEGIN
-
- { Save off the old GrafProcs, and install a new set. This new set includes a
- pointer to PictSizer, which will be used to measure the size of the final
- PICT. When we are all done, we’ll re-install the normal set of GrafProcs. }
-
- SELF.SetRWGrafProcs(oldProcs, newProcs, NIL, @PictSizer);
-
- { Init the size of the pict we are going to save. Start with picture header. }
-
- gPictSize := SizeOf(Picture);
-
- { Now go ahead and open the picture and build it in RAM. We would have done
- this by slices before, but the newer systems have a patch for playing back
- pictures that minimize the RAM hit, so we don’t have to worry about the full
- screen CopyBits here. }
-
- WITH thePort^ DO BEGIN
- gPictHandle := OpenPicture(portRect);
- SELF.DoDrawPicture;
- ClosePicture; { the picture is created, and packed. }
- END; { with thePort^ }
-
- { Done saving the size of the picture itself. Now set the GrafProcs back to normal. }
-
- thePort^.grafProcs := oldProcs;
-
- { Dispose the pict handle, we didn’t actually make anything there. }
-
- KillPicture(gPictHandle);
- gPictHandle := NIL;
-
- { The picture has been sized. Now add that in to the total size the file will use on
- disk, include the header for the file, plus the number of bytes in actual PICT. }
-
- dataForkBytes := dataForkBytes + gPictSize + kPICTHeaderSize;
-
- END; { TPICTDocument.DoNeedDiskSpace }
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE PictReader(dPointer: Ptr; nextHunk: Integer);
-
- { Bottleneck routine to read the picture from the disk. This will read the data required,
- and pass it along to the unpacker. This makes it possible to avoid using any RAM for the
- actual reading part, as it is being played back into the offscreen device. Error handling
- is somewhat tricky, since we need to force the picture to finish, and there isn’t a really
- good way to do this. The desired attempt here is to pass back a “picture is finished”
- opcode ($00FF) so we can get back to our code to handle the error. This is better than no
- error recovery, but is not guaranteed to work. }
-
- VAR
- longHunk: LONGINT;
- i: Integer;
-
- BEGIN
-
- IF gPictError = noErr THEN BEGIN
- longHunk := nextHunk;
- gPictError := FSRead(gPictRefNum, longHunk, dPointer);
- END;
-
- { Handle the error situation by passing back $00FF as the data. }
-
- IF gPictError <> noErr THEN BEGIN
- FOR i := 1 TO nextHunk DO BEGIN
- IF ODD(i) THEN
- dPointer^ := $00
- ELSE
- dPointer^ := - 1;
- dPointer := Ptr(ORD4(dPointer) + 1);
- END;
- END;
-
- END; { PictReader }
-
- {-------------------------------------------------------------------------------------------}
- {$S AReadFile}
-
- PROCEDURE TPICTDocument.DoRead(aRefNum: Integer; rsrcExists, forPrinting: Boolean); OVERRIDE;
-
- { Routine to read the data from the data fork of the file into our document so it can be
- displayed. The quickdraw bottleneck will be replaced with the PictReader routine, making
- it read the data from the disk as the picture requests more data. This obviates the need
- for an extra handle that is used to play back the picture. This is done since that extra
- handle can be on the order of 100K, memory we may not have available. }
-
- VAR
- anOffWorlder: TOffscreen;
- tempRect: Rect;
- recsize: LONGINT;
- fi: FailInfo;
- newProcs: CQDProcs;
- oldProcs: QDProcsPtr; { bug in include files, CGrafPort has
- QDProcs *** }
-
- PROCEDURE DeathRead(error: OSErr; message: LONGINT);
-
- BEGIN
- IF gPictHandle <> NIL THEN
- KillPicture(gPictHandle);
- gPictHandle := NIL;
- END;
-
- BEGIN
-
- { Make sure the file position is right at the start of the picture in the file. }
-
- FailOSErr(SetFPos(aRefNum, fsFromStart, kPICTHeaderSize));
-
- { Allocate a small handle that will be used as the Pict handle for drawing from
- the disk. This is just the picture header. }
-
- gPictHandle := PicHandle(NewHandle(SizeOf(Picture)));
- FailNil(gPictHandle);
-
- { If the read of the picture header fails, we want to dispose the handle allocated. }
-
- CatchFailures(fi, DeathRead);
-
- { Tell PictReader what file to read from. }
-
- gPictRefNum := aRefNum;
- gPictError := noErr;
-
- { Now fill in the picture header itself, using the data from the disk. }
-
- recsize := SizeOf(Picture);
- gPictError := FSRead(aRefNum, recsize, Ptr(gPictHandle^));
- FailOSErr(gPictError);
-
- { That is the only call we can’t recover from immediately, the rest of the
- routine is not easy to recover from, so we won’t go through DeathRead. }
-
- Success(fi);
-
- { The file position is right at the beginning of the picture data, so we can just
- install the bottleneck and call DrawPicture to fill our offscreen gDevice
- with the data that was saved. Set to that port and gDevice for playback. }
-
- { Save the pointer to the current CGrafProcs, and
- the install our our Pict proc in the grafProcs. }
-
- SELF.SetRWGrafProcs(oldProcs, newProcs, @PictReader, NIL);
-
- { We can now draw the picture that will be read out of the file into this port
- in order to init the port for later use in updating the window. We are already
- set to draw in the offscreen port. Do the DrawPicture to have PictReader read
- the data out of the file while it is being played into the offscreen Port. }
-
- DrawPicture(gPictHandle, gPictHandle^^.picFrame);
-
- { Done reading the data of the picture itself. Now set the GrafProcs back to normal. }
-
- thePort^.grafProcs := oldProcs;
-
- { Bag the handle we made for playing back the picture. }
-
- KillPicture(gPictHandle);
- gPictHandle := NIL;
-
- { If we had an error while reading the data, we must error out. }
-
- FailOSErr(gPictError);
-
- END; { TPICTDocument.DoRead }
-
- {-------------------------------------------------------------------------------------------}
- {$S AWriteFile}
-
- PROCEDURE PictWriter(dPointer: Ptr; nextHunk: Integer);
-
- { This routine will save the current image as it is created. As the data requests go by
- that data will be written to the file. The data is being created by the OpenPicture/
- CopyBits in DoWrite. This is the bottleneck for that operation. Any errors found while
- doing this will make us skip any further requests to write data to the disk. No memory is
- allocated. Communication with DoWrite is done through globals, since bottlenecks must be
- at the main level. The bottleneck must also keep track of how many bytes are written, so
- that the header on the picture can be fixed up to be correct. This must be done to avoid
- creating bogus pictures. The picSize field of the handle must be updated continuously so
- that when the picture is done, the ClosePicture can create a valid picture. The check for
- the NIL handle is to handle the problem of when the OpenPicture is called. The proc gets
- called before the handle is valid. Be very careful of these bottleneck things; it is easy
- to run into problems that are very hard to figure out. QuickDraw has no facilities to give
- you info when things go wrong so it makes it a bit tougher. }
-
- VAR
- longHunk: LONGINT;
-
- BEGIN
- IF gPictError = noErr THEN BEGIN
- longHunk := nextHunk;
- gPictError := FSWrite(gPictRefNum, longHunk, dPointer);
- gPictSize := gPictSize + longHunk;
- IF gPictHandle <> NIL THEN
- gPictHandle^^.picSize := LoWord(gPictSize);
- END; { if no error so far }
- END; { PictWriter }
-
- {-------------------------------------------------------------------------------------------}
- {$S AWriteFile}
-
- PROCEDURE TPICTDocument.DoWrite(aRefNum: Integer; makingCopy: Boolean); OVERRIDE;
-
- { Write the data calculated into the document to the file. This will make it a real PICT
- file. The file will be saved using QuickDraw Bottlenecks for the PutPicProc. As the data
- requests go by, they will be written to the file, using the PictWriter routine. }
-
- VAR
- recsize: LONGINT;
- fi: FailInfo;
- newProcs: CQDProcs;
- oldProcs: QDProcsPtr; { bug in include files, CGrafPort has
- QDProcs *** }
-
- PROCEDURE DeathWrite(error: OSErr; message: LONGINT);
-
- BEGIN
- IF gPictHandle <> NIL THEN
- KillPicture(gPictHandle);
- gPictHandle := NIL;
-
- thePort^.grafProcs := oldProcs;
- END;
-
- BEGIN
-
- { We need to write the picture data itself out to the file, after we set the
- mark to be after the entire header. Make sure the file is that big before we
- do it. Included in this set is the header of the picture itself, the 10 bytes
- that include the rectangle. Those bytes will be updated after the picture is
- written. }
-
- FailOSErr(SetEOF(aRefNum, kPICTHeaderSize + SizeOf(Picture)));
- FailOSErr(SetFPos(aRefNum, fsFromStart, kPICTHeaderSize + SizeOf(Picture)));
-
- { If the write of the picture header fails, we want to dispose the handle allocated. }
-
- CatchFailures(fi, DeathWrite);
-
- { The file is all set up to go. We now want to replace the QuickDraw
- bottleneck and create the actual Picture data. }
-
- SELF.SetRWGrafProcs(oldProcs, newProcs, NIL, @PictWriter);
-
- { Tell PictWriter what file to write to, and start the pic size including the
- picture header. Start all the pieces off the right way. }
-
- gPictRefNum := aRefNum;
- gPictSize := SizeOf(Picture);
- gPictError := noErr;
- gPictHandle := NIL;
-
- { Actually open the picture and do the CopyBits in order to process the picture.
- The data will be written by PictWriter as it is called by QuickDraw. }
-
- WITH thePort^ DO BEGIN
- gPictHandle := OpenPicture(portRect);
- ClipRect(portRect); { Make it a happier picture. }
-
- { copy all of the image to itself, in an open picture it saves the bits. }
-
- SELF.DoDrawPicture;
- ClosePicture; { the picture is created, and packed. }
- END; { with thePort^ }
-
- { Now check for errors during the write operation. The gPictError field will be
- nonzero if we failed during the operation. }
-
- FailOSErr(gPictError);
-
- { Move back to front of file and write the valid picture info to file. }
-
- FailOSErr(SetFPos(aRefNum, fsFromStart, kPICTHeaderSize));
- recsize := SizeOf(Picture);
- FailOSErr(FSWrite(aRefNum, recsize, Ptr(gPictHandle^)));
-
- { Done saving the data of the picture itself. Now set the GrafProcs back to
- normal. }
-
- thePort^.grafProcs := oldProcs;
-
- { Dispose the pict handle, we didn’t actually make anything there. }
-
- KillPicture(gPictHandle);
- gPictHandle := NIL;
-
- { If we lived through it, clear error handler. }
-
- Success(fi);
-
- END; { TPICTDocument.DoWrite }
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TPICTDocument.SetRWGrafProcs(VAR oldProcs: QDProcsPtr; VAR newProcs: CQDProcs;
- readProc, writeProc: ProcPtr);
-
- { Common routine to set the current port’s grafprocs for reading and writing. It gets called
- by our DoRead and DoWrite overrides to that we can read and write PICTs without causing a
- big memory hit. }
-
- BEGIN
- oldProcs := thePort^.grafProcs;
- SetStdCProcs(newProcs);
- thePort^.grafProcs := @newProcs;
-
- IF readProc <> NIL THEN
- newProcs.GetPicProc := readProc;
-
- IF writeProc <> NIL THEN
- newProcs.PutPicProc := writeProc;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S AFields}
-
- PROCEDURE TPICTDocument.Fields(PROCEDURE DoToField(fieldName: Str255; fieldAddr: Ptr;
- fieldType: Integer)); OVERRIDE;
-
- BEGIN
- DoToField('TPICTDocument', NIL, bClass);
-
- { Print fields of anscestors }
- INHERITED Fields(DoToField);
- END;
-
- {-------------------------------------------------------------------------------------------}
- {--------------------------------TFracAppDocument Methods-----------------------------------}
- {-------------------------------------------------------------------------------------------}
- {$S AOpen}
-
- PROCEDURE TFracAppDocument.IFracAppDocument(use32BitCQD: Boolean; startupMode: CmdNumber);
-
- { Init for the FracAppDocument itself. We set a couple of fields to NIL so that we can Fail
- gracefully, and then init the base TPICTDocument. When we come back, we store in the
- FracHeader what kind of offscreen stuff we want to use. }
-
- BEGIN
-
- fOffWorlder := NIL;
- fFracAppEngine := NIL;
- fFracAppWindow := NIL;
- fStartupMode := startupMode;
-
- SELF.IPICTDocument;
-
- { We just initialize ‘use32BitCQD’ here because this is where we get passed its
- value. The rest of fFracHeader gets initialized in DoInitialState. }
-
- fFracHeader.use32BitCQD := use32BitCQD;
-
- END; { TFracAppDocument.IFracAppDocument }
-
- {-------------------------------------------------------------------------------------------}
- {$S AClose}
-
- PROCEDURE TFracAppDocument.Free; OVERRIDE;
-
- { Free method for the documents themselves. We need to override so that we can throw away
- the data object that was read in from the disk if it exists. }
-
- BEGIN
- SELF.FreeData;
- INHERITED Free;
- END; { TFracAppDocument.Free }
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppDocument.AnimateColors;
-
- { Called during Idle time to do any palette manager animation that may be needed. Three
- conditions must be satisfied before we animate: 1) the user must select the right menu
- item, 2) we must be in the foreground, and 3) a decent amount of time has to have elapsed
- since we lat animated (we don’t want things to go whizzing by too fast on those faster
- Macs).
-
- If all of those conditions are satisfied, we animate. We first get a copy of the window’s
- current palette and put it into a color table with the Palette2Ctab call. We then shift up
- all the color entries, moving the one at the beginning to the end. Finally, we call
- AnimatePalette with our modified color table. }
-
- VAR
- currPalette: PaletteHandle;
- destCTab: CTabHandle;
- lastCSpec: ColorSpec;
- theWindow: WindowPtr;
- fi: FailInfo;
-
- PROCEDURE HdlAnimate(error: OSErr; message: LONGINT);
-
- BEGIN
- gAnimate := FALSE;
- END;
-
- BEGIN
- IF gAnimate & NOT gInBackGround & (TickCount > (gLastAnimated +
- kAnimationDelay)) THEN BEGIN
- theWindow := fFracAppWindow.fWMgrWindow;
-
- { Get the palette for our window. First attempt to get any palette that is
- directly attached to the window. If that doesn’t work, try to get the default
- application palette. Once we got one, convert it to a color table. }
-
- currPalette := GetPalette(theWindow);
- IF (currPalette = NIL) & gHasDefaultApplicationPalettes THEN
- currPalette := GetPalette(WindowPtr( - 1));
- FailNil(currPalette);
-
- CatchFailures(fi, HdlAnimate);
- destCTab := CTabHandle(NewHandle(SizeOf(ColorTable) + ((kNumColors + 16) *
- SizeOf(ColorSpec))));
- FailNil(destCTab);
- Palette2CTab(currPalette, destCTab);
- Success(fi);
-
- { Move the colors around in the color table, skipping the first 16, and moving
- all the elements down by one, and copying the element at 16 back to the end
- of the table. The effect is to rotate the colors in the table. }
-
- {$Push} {$R-} { Turn off range checking for ctTable }
- lastCSpec := destCTab^^.ctTable[16]; { pull first one off. }
- BlockMove(@destCTab^^.ctTable[17], @destCTab^^.ctTable[16], (kNumColors - 1) *
- SizeOf(ColorSpec)); { copy all one entry down. }
- destCTab^^.ctTable[kNumColors + 16 - 1] := lastCSpec; { put last color back on
- front. }
- {$Pop}
-
- AnimatePalette(theWindow, destCTab, 16, 16, kNumColors);
-
- DisposHandle(Handle(destCTab));
- gLastAnimated := TickCount;
- END;
- END; { TFracAppDocument.AnimateColors }
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppDocument.BumpAreaComplete(areaAmount: LONGINT);
-
- { Adds “areaAmount” to the areaComplete field. We keep track of this stuff so that we can
- display the percentage complete. This is my own method that the TFracAppEngines call when
- they’ve updated a new area of the fractal. }
-
- BEGIN
- SELF.SetAreaComplete(fFracHeader.areaComplete + areaAmount, kDontForceUpdate);
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppDocument.BumpChangeCount;
-
- { Increments the fChangeCount field. This is my own method that the TFracAppEngines call
- when they’ve updated a new area of the fractal. }
-
- BEGIN
- SELF.SetChangeCount(SELF.GetChangeCount + 1);
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppDocument.BumpCalculationTime(elapsedAmount: LONGINT);
-
- { Increments the elapsedTime field and updates the window display. Called by the document
- idling routine: CalcTown. }
-
- BEGIN
- SELF.SetCalculationTime(fFracHeader.elapsed + elapsedAmount, kDontForceUpdate);
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- FUNCTION TFracAppDocument.CalcTown: Boolean;
-
- { Document’s idling routine. Called by TFracAppApplication.DoIdle when it’s this document’s
- turn to calculate part of itself. Takes care of calling the TFracAppEngine, and updating
- the window timer displays.
-
- It determines how much time to give to the FracAppEngine in 3 ways. First, if we are in
- the background, we don’t want to be CPU hogs, so we take up as little time as possible.
- Mostly this mean limiting ourselves to about one call to the engine. If we are in the
- foreground we have two ways of determining time. If we have built a version of our program
- that doesn’t include the Performance Tools, then we use the Time Manager for seeing how
- much time (in milliseconds) we’ve taken up, and leaving after a certain amount of time. If
- we built a version of the program that could possibly be using the performance tools, then
- we can’t take advantage of the Time Manager as the Performance tools pre-empt its use. So
- we time ourselves by using Ticks.
-
- We prefer using the Time Manager, though. This is because use use the timing results here
- are used to keep track of total elapsed time. The Time Manager’s resolution is 1
- millisecond (1/1000 of a second) the way we use it here, whereas using Ticks gives us a
- resolution of 1/60 of a second. After a while, little errors accumulate, which are
- aggravated by the low resolution of TickCount. So we use the Time Manager, instead, to
- lessen the accumulated errors. }
-
- VAR
- MaxInterval: LONGINT;
- startTime: LONGINT;
- endTime: LONGINT;
- theDocumentFinishedCalculating: Boolean;
-
- BEGIN
- IF gInBackGround THEN
- MaxInterval := kBkCalcTime
- ELSE
- MaxInterval := kFgCalcTime;
-
- {$IFC qPerform}
-
- startTime := TickCount;
- endTime := startTime + MaxInterval;
- SELF.PreDraw;
- REPEAT
- theDocumentFinishedCalculating := fFracAppEngine.CalcCity;
- UNTIL theDocumentFinishedCalculating OR (TickCount >= endTime);
- SELF.PostDraw;
- endTime := TickCount;
-
- {$ELSEC}
-
- startTime := gCounter;
- endTime := startTime + MaxInterval;
- SELF.PreDraw;
- REPEAT
- theDocumentFinishedCalculating := fFracAppEngine.CalcCity;
- UNTIL theDocumentFinishedCalculating OR (gCounter >= endTime);
- SELF.PostDraw;
- endTime := gCounter;
-
- {$ENDC}
-
- { We’re done with calculating our part of the fractal for now. Now update our
- counters in the window. }
-
- SELF.BumpCalculationTime(endTime - startTime);
- SELF.SetElapsedTime(kDontForceUpdate);
-
- { If the engine signalled that we’re all done, then remember that. Also return that fact
- to the routine that called us so that it can take appropriate action. }
-
- fFracHeader.done := theDocumentFinishedCalculating;
- CalcTown := theDocumentFinishedCalculating;
-
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S AWriteFile}
-
- PROCEDURE TFracAppDocument.DoDrawPicture; OVERRIDE;
-
- { This method overrides the abstract method in TPICTDocument. It is called in the writing
- routines to image the data for the PICT. }
-
- BEGIN
-
- { The next couple of lines are really weird. Take a look at pages V 71-72 of
- Inside Mac. There, in the description of CopyBits, you’ll see that it says
- that “During a CopyBits call, the foreground and background colors are
- applied to the image.” Technote 163, “Adding Color with CopyBits” explains
- more on exactly what happens. What my point is, though, is that we don’t want
- this to happen. So to effectively get a null color addition, we set the
- foreground and background colors to black and white. }
-
- RGBForeColor(gRGBBlack);
- RGBBackColor(gRGBWhite);
-
- { Copy all of the image to itself. This has no effect on the grafPort, but it
- does cause all of the data to get captured by the PICT grafProcs. }
-
- WITH thePort^ DO
- CopyBits(portBits, portBits, portRect, portRect, srcCopy, NIL);
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S AOpen}
-
- PROCEDURE TFracAppDocument.DoInitialState; OVERRIDE;
-
- { Does the work for a New operation, where we start with a new fractal that doesn’t have any
- stored data. This is to set up the view with no data and set up the fractal coordinates to
- the default. }
-
- VAR
- anOffWorlder: TOffscreen;
- aFracAppEngine: TFracAppEngine;
- tempRect: Rect;
- versionToMake: Integer;
-
- BEGIN
- WITH fFracHeader DO BEGIN
-
- { Start by filling in the fields that identify this document. These fields will
- be examined when we read the document back in from disk. If we don’t
- recognize them, we bail out of reading them back in. The fType field is set
- to our file’s signature. The hdrID identifies this header as one that is
- associated with FracApp. Finally, the version number identifies which FracApp
- engine we are using. At this point, we set it to kUnknownVersion, because we
- don’t know how the engine will identify itself; we rely on it to ultimately
- fill this field correctly. }
-
- fType := kSignature;
- hdrId := Integer(kHeaderID);
- version := kUnknownVersion;
-
- done := FALSE; { not done, starting brand new document.
- }
- elapsed := 0; { just starting, set the time spent on
- calculating the fractal to zip. }
- {$Push} {$H-}
- GetDateTime(startingTime);
- {$Pop}
- endingTime := 0;
- areaComplete := 0;
-
- IF fStartupMode IN [cNewFromSelection, cNewMultiPage,
- cContinuingMultiPage] THEN BEGIN
-
- { We are being created from a previous document. This is either as a result of
- a multipage process, or because the user selected a range and told us to zoom
- in on it. In either case, all the information we need to boot up is in
- gPageRecord. }
-
- pages := gPageRecord;
-
- IF fStartupMode IN [cNewMultiPage, cContinuingMultiPage] THEN
- pages.multiPaging := TRUE
- ELSE
- pages.multiPaging := FALSE;
-
- END
- ELSE BEGIN
-
- { We start from scratch. This is the standard set of coordinates to start the
- default Mandelbrot set. Set up the coordinates to do, saving state in header
- variables. The other variables (currentH, currentV, maxH, maxV) are not
- needed if we are not multipaging. Since this part of the IF/THEN statement
- doesn’t handle multipaging, we don’t have to futz with them. }
-
- WITH pages DO BEGIN
- RealMin := - 2.5;
- RealMax := 1.5;
- ImagMin := - 1.5232;
- ImagMax := 1.5232;
- multiPaging := FALSE;
- END;
- END;
-
- { Set the fractal rectangle to be the full page size at 300 dpi. }
-
- {$Push} {$H-}
- SetRect(calcRect, 0, 0, kRectRight, kRectBottom);
- {$Pop}
-
- { Set up the width/height of fractal, the delta in each axis as a real number,
- and ensure that the starting min/max values for the figure are set to supply
- a 1:1 aspect ratio. }
-
- { Set up the iterations by calculating up the step constants, and the
- edges of the view area in pixels. }
-
- plotWidth := (calcRect.right - calcRect.left);
- plotHeight := (calcRect.bottom - calcRect.top);
- deltaP := (pages.RealMax - pages.RealMin) / (plotHeight - 1);
- deltaQ := (pages.ImagMax - pages.ImagMin) / (plotWidth - 1);
-
- { Force aspect ratio 1:1, making delta smallest of two. This effectively grows
- one side or the other out, like rMax/iMax becoming bigger number. }
-
- IF deltaP > deltaQ THEN BEGIN
- deltaQ := deltaP;
- pages.ImagMax := deltaQ * (plotWidth - 1) + pages.ImagMin;
- END { grow the q side }
- ELSE BEGIN
- deltaP := deltaQ;
- pages.RealMax := deltaP * (plotHeight - 1) + pages.RealMin;
- END; { grow the p side }
- END; { With FracHeader }
-
- { Create our supporting objects, the OffWorld object, and the Fractal engine object. }
-
- tempRect := fFracHeader.calcRect;
- anOffWorlder := SELF.MakeOffWorlder(fFracHeader.use32BitCQD, tempRect);
- fOffWorlder := anOffWorlder;
-
- CASE gUseFastAlgorithm OF
- TRUE: versionToMake := kFastVersion;
- FALSE: versionToMake := kNormalVersion;
- END;
-
- aFracAppEngine := SELF.MakeFracAppEngine(versionToMake);
- fFracAppEngine := aFracAppEngine;
-
- END; { TFracAppDocument.DoInitialState }
-
- {-------------------------------------------------------------------------------------------}
- {$S AOpen}
-
- PROCEDURE TFracAppDocument.DoMakeViews(forPrinting: Boolean); OVERRIDE;
-
- { Make something to see our picture in. This uses NewTemplateWindow to create a window and
- its subviews from a ‘view’ resource. When we have the window, we read in a ‘pltt’ resource
- and attach it to it. We also initialize a lot of the windows fields with references to its
- subviews. Finally, we create a print handler, and attach it to the TFracAppView. }
-
- VAR
- theWindow: TFracAppWindow;
- aHandler: TStdPrintHandler;
-
- BEGIN
-
- theWindow := TFracAppWindow(NewTemplateWindow(kFracAppWindowID, SELF));
- fFracAppWindow := theWindow;
- MySetPalette(theWindow);
-
- WITH theWindow DO BEGIN
- fFracAppView := TFracAppView(FindSubView('FRAC'));
- fCTimeLabelView := TStaticText(FindSubView('tCTL'));
- fCTimeView := TStaticText(FindSubView('tCTm'));
- fETimeLabelView := TStaticText(FindSubView('tETL'));
- fETimeView := TStaticText(FindSubView('tETm'));
- fMethodNameView := TStaticText(FindSubView('tTYP'));
- fPercentView := TStaticText(FindSubView('tPCT'));
- fPercentLabelView := TStaticText(FindSubView('tPCL'));
- fSingleBarView := TControl(FindSubView('sBAR'));
- END;
-
- SELF.SetAreaComplete(fFracHeader.areaComplete, kForceUpdate);
- SELF.SetCalculationTime(fFracHeader.elapsed, kForceUpdate);
- SELF.SetElapsedTime(kForceUpdate);
- SELF.SetMethodName(kForceUpdate);
-
- { Printing handler as standard print handler will get a color port back
- from the driver. The pixels are square, and the dimensions are fixed. }
-
- New(aHandler);
- FailNil(aHandler);
- aHandler.IStdPrintHandler(SELF, { its document }
- fFracAppWindow.GetFracAppView, { its view }
- kSquareDots, { has square dots }
- kFixedSize, { horzontal page size is fixed }
- kFixedSize); { vertical page size is fixed }
-
- END; { TFracAppDocument.DoMakeViews }
-
- {-------------------------------------------------------------------------------------------}
- {$S ADoCommand}
-
- FUNCTION TFracAppDocument.DoMenuCommand(aCmdNumber: CmdNumber): TCommand; OVERRIDE;
-
- { The document is in charge of menu items that affect the document. In FracApp, the only
- things that should be active when we have a document are the normal editting commands, and
- the palette munging stuff. We don’t support Cut or Paste, which don’t really make sense in
- FracApp, but we support Copy. Doing a Copy will put the current selection to the desk
- scrap as a PICT. We rely on the default MacApp clipboard routines to display the PICT. }
-
- BEGIN
- DoMenuCommand := gNoChanges;
- CASE aCmdNumber OF
-
- cAnimate: BEGIN
- gAnimate := NOT gAnimate;
- END;
-
- cJumble: BEGIN
- gPaletteIsJumbled := NOT gPaletteIsJumbled;
- IF gPaletteIsJumbled THEN
- SELF.JumblePalette
- ELSE
- SELF.RestorePalette;
- END;
-
- cNormal: BEGIN
- gAnimate := FALSE;
- gPaletteIsJumbled := FALSE;
- SELF.RestorePalette;
- END;
-
- OTHERWISE DoMenuCommand := INHERITED DoMenuCommand(aCmdNumber);
- END;
- END; { TFracAppDocument.DoMenuCommand }
-
- {-------------------------------------------------------------------------------------------}
- {$S AWriteFile}
-
- PROCEDURE TFracAppDocument.DoNeedDiskSpace(VAR dataForkBytes,
- rsrcForkBytes: LONGINT); OVERRIDE;
-
- { DoNeedDiskSpace is called when we need to estimate the size the file will be on disk. Most
- of the work is done by the base class, TPICTDocument. It figures out how large the file
- will be when converted into a PICT. However, before we can call it, we have to switch to
- the fractal image off screen. We then call the INHERITED method, and switch back after
- we’re all done. }
-
- BEGIN
- SELF.PreDraw;
- INHERITED DoNeedDiskSpace(dataForkBytes, rsrcForkBytes);
- SELF.PostDraw;
- END; { TFracAppDocument.DoNeedDiskSpace }
-
- {-------------------------------------------------------------------------------------------}
- {$S AReadFile}
-
- PROCEDURE TFracAppDocument.DoRead(aRefNum: Integer; rsrcExists,
- forPrinting: Boolean); OVERRIDE;
-
- { Called to read in the file. We let the base class, TPICTDocument, handle reading in the
- actual PICT data. Here, we make sure that we are properly set to the offscreen world first
- so that we image to the right place. We also read in the user header data into our
- FracHeader record. }
-
- VAR
- recsize: LONGINT;
- tempRect: Rect;
- anOffWorlder: TOffscreen;
- aFracAppEngine: TFracAppEngine;
- fi: FailInfo;
-
- PROCEDURE DeathRead(error: OSErr; message: LONGINT);
-
- BEGIN
- SELF.PostDraw;
- END;
-
- BEGIN
-
- { The file is open already, we just have to read the data out of it. The first
- thing to read is the header we use to describe a fractal. If we get an error
- here we need to split since we should always have at least a header. The
- fractal header is the global state for the document. We just read it into the
- record and use it from there. }
-
- FailOSErr(SetFPos(aRefNum, fsFromStart, 0)); { starts at first byte of file. }
- recsize := SizeOf(fFracHeader); { size of header on fractal files. }
- FailOSErr(FSRead(aRefNum, recsize, @fFracHeader));
-
- { We have the header for the PICT file. Now we need to be sure that it is a
- fractal document, and not something we can’t use. Check the header to be
- sure, and if not right, error out with a good alert message (using a standard
- MacApp errcode). }
-
- IF fFracHeader.fType <> kSignature THEN BEGIN
- IF qDebug THEN BEGIN
- WrLblSig('Failing in DoRead. Signature', fFracHeader.fType);
- writeln;
- WrLblSig(' should be', kSignature);
- writeln;
- END;
- FailOSErr(errNotMyType);
- END;
-
- IF fFracHeader.hdrId <> Integer(kHeaderID) THEN BEGIN
- IF qDebug THEN BEGIN
- WrLblHexInt('Failing in DoRead. hdrId', fFracHeader.hdrId);
- writeln;
- WrLblHexInt(' should be', Integer(kHeaderID));
- writeln;
- END;
- FailOSErr(errNotMyType);
- END;
-
- { We have the data from the header. Go ahead and set up an offscreen world for
- this document, using the header rectangle. Once we got that, switch over to
- it, and read in the PICT stuff by calling the INHERITED DoRead method. }
-
- tempRect := fFracHeader.calcRect;
- anOffWorlder := SELF.MakeOffWorlder(fFracHeader.use32BitCQD, tempRect);
- fOffWorlder := anOffWorlder;
-
- CatchFailures(fi, DeathRead);
- SELF.PreDraw;
- INHERITED DoRead(aRefNum, rsrcExists, forPrinting);
- SELF.PostDraw;
- Success(fi);
-
- { Now we’ve read in the standard header, and have imaged the PICT into the
- offscreen PixMap. All we have to do now is create a fractal engine, and
- initialize from the rest of the data on this disk. }
-
- aFracAppEngine := SELF.MakeFracAppEngine(fFracHeader.version);
- fFracAppEngine := aFracAppEngine;
-
- { Set the file mark back to the end of the FracHeader. This is done as our last
- action so that anything subclassing us can read any information it appends to
- the normal FracHeader. It shouldn’t know what size the FracRecord is (or even
- really know that it exists), or realize that there is variable length engine
- data appended to it, so we reposition the file mark for it. }
-
- FailOSErr(SetFPos(aRefNum, fsFromStart, SizeOf(fFracHeader)));
- fFracAppEngine.DoRead(aRefNum, rsrcExists, forPrinting);
-
- END; { TFracAppDocument.DoRead }
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppDocument.DoSetupMenus; OVERRIDE;
-
- { Set up the menus that the document is responsible for. These are the ones that logically
- should only be enabled if we have a document on the screen. In our case, this includes the
- Copy Edit command, and the Palette Manager animation commands. }
-
- BEGIN
- INHERITED DoSetupMenus; { Do mainline stuff first. }
-
- EnableCheck(cAnimate, gConfiguration.has32BitQD, gAnimate);
- EnableCheck(cJumble, gConfiguration.has32BitQD, gPaletteIsJumbled);
- Enable(cNormal, gConfiguration.has32BitQD);
- END; { TFracAppDocument.DoSetupMenus }
-
- {-------------------------------------------------------------------------------------------}
- {$S AWriteFile}
-
- PROCEDURE TFracAppDocument.DoWrite(aRefNum: Integer; makingCopy: Boolean); OVERRIDE;
-
- { Called to write out the file. We let the base class, TPICTDocument, handle writing out the
- actual PICT data. Here, we make sure that we are properly set to the offscreen world first
- so that we copybits from the right place. We also write out the user header data from our
- FracHeader record and fractal engine. }
-
- VAR
- recsize: LONGINT;
- fi: FailInfo;
-
- PROCEDURE DeathWrite(error: OSErr; message: LONGINT);
-
- BEGIN
- fOffWorlder.ChangeCTFlag(kAnimationFlag, kSetFlag);
- SELF.PostDraw;
- END;
-
- BEGIN
-
- { Prepare our world for writing the PICT out. First, we do a CatchFailures so
- that we can restore the world to a reasonable state in case something goes
- wrong. Then we switch in the offscreen world. The next step is kind of ugly,
- but necessary. We clear bit 14 of ctFlags before writing out the file. This
- is because we’ll run into problems later if we try to read the PICT back in
- with that bit set; we won’t match the colors that we would like in our
- offscreen world.
-
- Bit 14 is the bit that tells 32-bit Color QuickDraw to match palette entries
- when copying from one pixmap to another. However, when we are reading from
- disk, the destination is our offscreen pixmap, which doesn’t have a palette.
- If bit 14 is set, 32bCQD will fall back on a backup plan of trying to match
- RGB values. So our solution is to clear bit 14. Finally, we call the
- INHERITED method to write out the PICT. After that’s done, we put everything
- back, and proceed to the part where we write out the header info. }
-
- CatchFailures(fi, DeathWrite);
- SELF.PreDraw; { Swap in our offscreen world }
- fOffWorlder.ChangeCTFlag(kAnimationFlag, kClearFlag);
- INHERITED DoWrite(aRefNum, makingCopy);
- fOffWorlder.ChangeCTFlag(kAnimationFlag, kSetFlag);
- SELF.PostDraw;
- Success(fi);
-
- { We have legit data in our document, set the mark in the file to be at the front.
- and write out the header information. Once that’s done, we call the Fractal
- engine to write out anything that it needs to. Notice that we do this last, so
- that we leave the file mark at the next available spot to write. This is so any
- thing that sub-classes us will be able to write right where we left them. }
-
- FailOSErr(SetFPos(aRefNum, fsFromStart, 0));
- recsize := SizeOf(fFracHeader);
- FailOSErr(FSWrite(aRefNum, recsize, @fFracHeader));
- fFracAppEngine.DoWrite(aRefNum, makingCopy);
-
- END; { TFracAppDocument.DoWrite }
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppDocument.FreeData; OVERRIDE;
-
- { This is typically used in a Revert case which is not really meaningful here, but the
- structure is the same so we use it anyway. Frees the data associated with a document,
- that is strictly program data, not MacApp data. }
-
- BEGIN
- FreeIfObject(fOffWorlder);
- FreeIfObject(fFracAppEngine);
- END; { TFracAppDocument.FreeData }
-
- {-------------------------------------------------------------------------------------------}
- { Access methods. These are here so that objects can query the document for some bits of
- information. Rather than have those objects just look into our instance variables and pull
- out what they need, we have them call these methods. This is so the actual implementation
- of the document data is kept private and, hence, flexible. For instance, an earlier
- version of this program had TFracAppView go into TFracAppDocument and take what it needed.
- This was done for speed purposes, as calling a function to return a value for you is much
- slower than just getting it yourself. However, this caused problems when the world changed
- with FracApp 2.0. Suddenly, document data was managed with TOffscreen objects. This
- necessitated modifying TFracAppView because the data wasn’t where it thought it was any
- more. Only the document should know how its data is managed and stored. Now that
- TFracAppView uses these accessors, we hopefully won’t have to make any changes to it in
- the future. }
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- FUNCTION TFracAppDocument.GetDone: Boolean;
-
- BEGIN
- GetDone := fFracHeader.done;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- FUNCTION TFracAppDocument.GetFracHeader: FracRecord;
-
- BEGIN
- GetFracHeader := fFracHeader;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- FUNCTION TFracAppDocument.GetFracPort: CGrafPtr;
-
- BEGIN
- GetFracPort := fOffWorlder.GetOffPort;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- FUNCTION TFracAppDocument.GetOffPixBase: Ptr;
-
- { Returns the pointer to the bits for the offscreen port. Called by the TFracAppEngine when
- doing its own version of GetCPixel. }
-
- BEGIN
- GetOffPixBase := fOffworlder.GetOffPixBase;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- FUNCTION TFracAppDocument.GetPlotHeight: LONGINT;
-
- BEGIN
- GetPlotHeight := fFracHeader.plotHeight;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- FUNCTION TFracAppDocument.GetPlotWidth: LONGINT;
-
- BEGIN
- GetPlotWidth := fFracHeader.plotWidth;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- FUNCTION TFracAppDocument.IsMultiPaging: Boolean;
-
- BEGIN
- IsMultiPaging := fFracHeader.pages.multiPaging;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppDocument.JumblePalette;
-
- { Called in response to the user selecting the “Jumble Palette” menu item. This takes our
- color table, rotates every third color 65 position up, another every third color 65
- positions down, and leaves the rest alone. It then calls AnimatePalette with this rotated
- color table. }
-
- VAR
- newcolors: CTabHandle;
- tempRGB: RGBColor;
- i: Integer;
-
- BEGIN
- newcolors := gOurColors;
- FailOSErr(HandToHand(Handle(newcolors)));
- FailNil(newcolors);
- WITH newcolors^^ DO BEGIN
- FOR i := 0 TO (63 DIV 3) DO BEGIN
- {[f-]} {$Push}{$R-}
- tempRGB := ctTable[i*3+1].rgb;
- ctTable[i*3+1].rgb := ctTable[i*3+66].rgb;
- ctTable[i*3+66].rgb := ctTable[i*3+131].rgb;
- ctTable[i*3+131].rgb := tempRGB;
-
- tempRGB := ctTable[i*3+132].rgb;
- ctTable[i*3+132].rgb := ctTable[i*3+67].rgb;
- ctTable[i*3+67].rgb := ctTable[i*3+2].rgb;
- ctTable[i*3+2].rgb := tempRGB;
- {[f+]} {$Pop}
- END;
- END;
- AnimatePalette(fFracAppWindow.fWMgrWindow, newcolors, 1, 16, kNumColors);
- DisposHandle(Handle(newcolors));
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppDocument.LockThePixels(lockIt: Boolean);
-
- { Called by the TFracAppView before it does any CopyBits operations. This is so the View
- only needs to know about the TFracAppDocument; it never needs to know that a TOffScreen
- object exists. }
-
- BEGIN
- IF lockIt THEN
- fOffWorlder.LockThePixels
- ELSE
- fOffWorlder.UnlockThePixels;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- FUNCTION TFracAppDocument.MakeFracAppEngine(version: Integer): TFracAppEngine;
-
- { All of the know-how that generates a fractal is encapsulated in the TFracAppEngine object.
- We’ll create a TNormalFracAppEngine to calculate the fractal on a pixel-by-pixel basis, or
- a TFastFracAppEngine if we want to use the Mariani/Silver algorithm. This method creates
- the object based on the version number passed to it, and return it to the caller. }
-
- VAR
- aNormalFracAppEngine: TNormalFracAppEngine;
- aFastFracAppEngine: TFastFracAppEngine;
-
- BEGIN
- CASE version OF
- kNormalVersion: BEGIN
- New(aNormalFracAppEngine);
- FailNil(aNormalFracAppEngine);
- aNormalFracAppEngine.INormalFracAppEngine(SELF);
- MakeFracAppEngine := aNormalFracAppEngine;
- END;
- kFastVersion: BEGIN
- New(aFastFracAppEngine);
- FailNil(aFastFracAppEngine);
- aFastFracAppEngine.IFastFracAppEngine(SELF);
- MakeFracAppEngine := aFastFracAppEngine;
- END;
- OTHERWISE BEGIN
- IF qDebug THEN
- writeln('Failing in MakeFracAppEngine. version = ', version);
- FailOSErr(errNotMyType);
- END;
- END;
- END; { TFracAppDocument.MakeFracAppEngine }
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- FUNCTION TFracAppDocument.MakeOffWorlder(use32BitCQD: Boolean; bounds: Rect): TOffscreen;
-
- { All the offscreen management will be handled by a TOffscreen object. Depending on whether
- or not 32-bit Color QuickDraw is available, we will either create an object that knows how
- to handle the new calls, or one that does things the old fashioned way. Once we have one
- of those, we use it for creating our offworld and for preparing it for being drawn into. }
-
- VAR
- aNewOffWorlder: TNewCoolOffscreen;
- anOldOffWorlder: TOldGrossOffscreen;
-
- BEGIN
- IF (use32BitCQD & gConfiguration.has32BitQD) THEN BEGIN
- New(aNewOffWorlder);
- FailNil(aNewOffWorlder);
- aNewOffWorlder.INewCoolOffscreen(bounds, gOurColors);
- MakeOffWorlder := aNewOffWorlder;
- END
- ELSE BEGIN
- New(anOldOffWorlder);
- FailNil(anOldOffWorlder);
- anOldOffWorlder.IOldGrossOffscreen(bounds, gOurColors);
- MakeOffWorlder := anOldOffWorlder;
- END;
- END; { TFracAppDocument.MakeOffWorlder }
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppDocument.PreDraw;
-
- { Called by TFracAppEngine before it images to the offscreen bitmap. This is so the Engine
- only needs to know about the TFracAppDocument; it never needs to know that a TOffScreen
- object exists. }
-
- BEGIN
- fOffWorlder.PreDraw;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppDocument.PostDraw;
-
- { Access method to our offscreen management to restore our original world that was swapped
- out with TFracAppDocument.PreDraw. }
-
- BEGIN
- fOffWorlder.PostDraw;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppDocument.ReportRectCompleted(dirtyRect: Rect);
-
- { Called by the TFracAppEngine when an area of the fractal has been completed and needs to
- be copied to the screen. }
-
- BEGIN
- fFracAppWindow.GetFracAppView.InvalidRect(dirtyRect);
- WITH dirtyRect DO BEGIN
- SELF.BumpAreaComplete((right - left) * (bottom - top));
- END;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppDocument.RestorePalette;
-
- { This effectively nullifies the effects of AnimateColors or JumblePalette. It makes sure
- that the color tables for our offscreen world and our gDevices match again. }
-
- BEGIN
- AnimatePalette(fFracAppWindow.fWMgrWindow, gOurColors, 1, 16, kNumColors);
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppDocument.SetAreaComplete(newComplete: LONGINT; forceUpdate: Boolean);
-
- { Updates the areaCompleted field in fFracHeader. Also updates the percentage field in the
- window. }
-
- VAR
- newPctComplete: Integer;
- oldPctComplete: Integer;
- pictureArea: LONGINT;
- pctString: Str255;
-
- BEGIN
- WITH fFracHeader DO BEGIN
- pictureArea := plotWidth * plotHeight;
- oldPctComplete := (areaComplete * 100) DIV pictureArea;
- newPctComplete := (newComplete * 100) DIV pictureArea;
- areaComplete := newComplete;
- END;
-
- IF forceUpdate | (oldPctComplete <> newPctComplete) THEN BEGIN
- NumToString(newPctComplete, pctString);
- fFracAppWindow.GetPercentView.SetText(pctString, kRedraw);
- END;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppDocument.SetCalculationTime(newElapsed: LONGINT; forceUpdate: Boolean);
-
- { Updates the elapsedTime field in fFracHeader. Also updates the calculation time field in
- the window’s infobar. }
-
- VAR
- timeString: Str255;
- newTime: Longint;
-
- BEGIN
-
- { Find the new time, and compare it with the time that currently appears in the
- window. If they are different then call IUTimePString to convert the time
- into a string, and then update the string in the window.
-
- kUnitsPerTime is a constant that converts our counter into a number expressed
- in seconds. Our counters are either expressed in Ticks or milliseconds.
- kUnitsPerTime is the right number to convert them to seconds. }
-
- newTime := newElapsed DIV kUnitsPerSecond;
-
- IF forceUpdate | (newTime > (fFracHeader.elapsed DIV kUnitsPerSecond)) THEN BEGIN
- IUTimePString(newTime, kWantSeconds, timeString, gIntlHandle);
- fFracAppWindow.GetCTimeView.SetText(timeString, kRedraw);
- END;
-
- fFracHeader.elapsed := newElapsed;
-
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppDocument.SetElapsedTime(forceUpdate: Boolean);
-
- { Updates the elapsed time field in the window’s infobar based on the starting and current
- time. }
-
- CONST
- kWantSeconds = TRUE;
-
- VAR
- timeString: Str255;
- newElapsed: LONGINT;
- currentTime: LONGINT;
-
- BEGIN
-
- { Determine what to use for our “ending value” for the elapsed time. Normally,
- this is just the current time. However, if the document is done, then the
- counters should be stopped, in which case, the “ending value” is the last
- value stored in fFracHeader.endingTime. }
-
- IF SELF.GetDone THEN
- currentTime := fFracHeader.endingTime
- ELSE
- GetDateTime(currentTime);
- newElapsed := currentTime - fFracHeader.startingTime;
-
- { If the current elapsed time is different from the time displayed in the
- window, convert it into a string with IUTimePString, and update the string in
- the window. }
-
- IF forceUpdate | (currentTime > fFracHeader.endingTime) THEN BEGIN
- IUTimePString(newElapsed, kWantSeconds, timeString, gIntlHandle);
- fFracAppWindow.GetETimeView.SetText(timeString, kRedraw);
- END;
-
- fFracHeader.endingTime := currentTime;
-
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppDocument.SetMethodName(forceUpdate: Boolean);
-
- { Sets the string that displays the algorithm being used to create the fractal. Called by
- TFracAppDocument.DoMakeViews when the document is being created. }
-
- VAR
- methodString: Str255;
- stringNumber: Integer;
- theString: Str255;
-
- BEGIN
-
- { Get the first part of the equation. Our final string will look something like
- “Using <Calculation Algorithm>/<Offscreen routines>”. We first get the string
- for “Using ”. }
-
- GetIndString(methodString, kAlgorithmStrings, kUsing);
-
-
- { Next, get the name for the algorithm. Convert the version number in
- fFracHeader into a string index so we can call GetIndString. If we don’t know
- the version number, punt to a string that says “Unknown.” This should never
- happen, but it doesn’t hurt to play it safe. When we get the string, add it
- to the first part. We now have “Using <Calculation Algorithm>”. }
-
- CASE fFracHeader.version OF
- kNormalVersion: stringNumber := kNormalAlgorithm;
- kFastVersion: stringNumber := kFastAlgorithm;
- OTHERWISE stringNumber := kUnknownAlgorithm;
- END;
- GetIndString(theString, kAlgorithmStrings, stringNumber);
- methodString := ConCat(methodString, theString);
-
-
- { If we don’t know what algorithm we are using, then we are done. Otherwise, we
- need to add the string that says what offscreen routines we are using. Get
- the slash, add it to the string, and then get the string that describes the
- offscreen routines. }
-
- IF stringNumber <> kUnknownAlgorithm THEN BEGIN
- GetIndString(theString, kAlgorithmStrings, kSlash);
- methodString := ConCat(methodString, theString);
- CASE fFracHeader.use32BitCQD OF
- TRUE: stringNumber := k32CQDRoutines;
- FALSE: stringNumber := kHomebrewRoutines;
- END;
- GetIndString(theString, kAlgorithmStrings, stringNumber);
- methodString := ConCat(methodString, theString);
- END;
-
- fFracAppWindow.GetMethodView.SetText(methodString, kRedraw);
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppDocument.SetVersion(version: Integer);
-
- { Called by the TFracAppEngine when it initializes itself. The engine is responsible for
- keeping track of what version document is created. Calling SetVersion is its way of
- informing the document what version it is. }
-
- BEGIN
- fFracHeader.version := version;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppDocument.StashSelectedRange;
-
- { Handy routine that stores information describing the current selection to the global
- variable ‘gPageRecord’. This is called when creating a new document based on the current
- selection in another document. }
-
- VAR
- selection: Rect;
-
- BEGIN
-
- selection := fFracAppWindow.GetFracAppView.GetSelection;
-
- { The following WITH statement is a bit obtuse. Here is prototype statement
- that translates what is really going on. }
-
- { gPageRecord.RealMin := fFracHeader.pages.RealMin + fFracHeader.deltaP * selection.top; }
-
- WITH fFracHeader, pages, selection DO BEGIN
- gPageRecord.RealMin := RealMin + deltaP * top;
- gPageRecord.ImagMin := ImagMin + deltaQ * left;
- gPageRecord.RealMax := RealMin + deltaP * bottom;
- gPageRecord.ImagMax := ImagMin + deltaQ * right;
- END;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S AFields}
-
- PROCEDURE TFracAppDocument.Fields(PROCEDURE DoToField(fieldName: Str255; fieldAddr: Ptr;
- fieldType: Integer)); OVERRIDE;
-
- BEGIN
- DoToField('TFracAppDocument', NIL, bClass);
- {$Push} {$H-}
- ShowFracHeader('fFracHeader', fFracHeader, DoToField);
- {$Pop}
- DoToField('fFracAppEngine', @fFracAppEngine, bObject);
- DoToField('fOffWorlder', @fOffWorlder, bObject);
- DoToField('fFracAppWindow', @fFracAppWindow, bObject);
- DoToField('fStartupMode', @fStartupMode, bInteger);
-
- { Print fields of anscestors }
- INHERITED Fields(DoToField);
- END;
-
- {-------------------------------------------------------------------------------------------}
- {---------------------------------TFracAppWindow Methods------------------------------------}
- {-------------------------------------------------------------------------------------------}
-
- {$S AOpen}
-
- PROCEDURE TFracAppWindow.IRes(itsDocument: TDocument; itsSuperView: TView;
- VAR itsParams: Ptr); OVERRIDE;
-
- { Initialize our window object. Overridden to set our instance variables to NIL. }
-
- BEGIN
- fCTimeLabelView := NIL;
- fCTimeView := NIL;
- fETimeLabelView := NIL;
- fETimeView := NIL;
- fFracAppView := NIL;
- fMethodNameView := NIL;
- fPercentView := NIL;
- fPercentLabelView := NIL;
- fSingleBarView := NIL;
-
- INHERITED IRes(itsDocument, itsSuperView, itsParams);
-
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ANonRes}
-
- PROCEDURE TFracAppWindow.AdjustInfoBar(width, height: VCoordinate; invalidate: Boolean);
-
- { Called by TFracAppWindow.Show and Resize to make sure the items in the infobar and the
- percentage complete label are all in the right places. }
-
- CONST
- kMaxPctString = '000'; { String representing the largest string that will be in the Percent Completed field. }
- kInfoBarMargin = 5; { Margin to use on the left and right sides of the infobar. }
- kLabelMargin = 5; { Margin to use between a time display and its label. }
- kMinimumSpacing = 15; { Minimum space to maintain between fields. }
- kTimeSlop = 0; { Slop to add to time elements. }
- kStaticTextSlop = 1; { Slop to add in GetTextWidth. The width of our
- TStaticText items can’t just be the width of the string. We also have to add a 1 pixel
- slop. This is because imaging is done with TextEdit, which inserts a 1 pixel margin
- at the left so that it can put an insertion point there. We have to take this into account
- when sizing our fields. }
-
- TYPE
-
- { We define this record to keep track of the new locations of our fields. Each
- one needs a newLocation specification, as well as a horizontal size (we never
- change an item’s height). We also keep a newEnd field here, which is
- newLocation.h + newSize, for convenience. }
-
- NewSpecs = RECORD
- newLocation: Point;
- newEnd: Integer;
- newSize: Integer;
- END;
-
- VAR
- itsText: Str255;
- itsStyle: TextStyle;
- specCTimeLabel: NewSpecs;
- specCTime: NewSpecs;
- specETimeLabel: NewSpecs;
- specETime: NewSpecs;
- specMethodName: NewSpecs;
- specPercentLabel: NewSpecs;
- specPercent: NewSpecs;
- specSingleBar: NewSpecs;
- rightEdge: Integer;
- topEdge: Integer;
- bottomScrollbar: TScrollbar;
-
- FUNCTION GetTextWidth(itsText: Str255; itsTextStyle: TextStyle): Integer;
-
- { Handy subroutine that takes a string and a style that it will be imaged with.
- It returns the width of that string in that style, adding in kStaticTextSlop. }
-
- VAR
- oldPort: GrafPtr;
-
- BEGIN
- GetPort(oldPort);
- SetPort(gWorkPort);
- SetPortTextStyle(itsTextStyle);
- GetTextWidth := StringWidth(itsText) + kStaticTextSlop;
- SetPort(oldPort);
- END;
-
- PROCEDURE MoveView(theView: TView; theSpecs: NewSpecs);
-
- { Subroutine that moves and sizes “theView” according to “theSpecs” }
-
- BEGIN
- WITH theView, theSpecs DO BEGIN
- Locate(newLocation.h, newLocation.v, invalidate);
- Resize(newSize, fSize.v, invalidate);
- END;
- END;
-
- BEGIN
-
- { We don’t want to do anything if none of our subviews exists yet. It is
- possible for this routine to get called before the window is completely
- built, in which case our subviews might not be created yet. Check to see if
- they exists, and exit if they don’t. }
-
- IF (fCTimeLabelView = NIL) THEN
- EXIT(AdjustInfoBar);
-
- { Do the leftmost item. This is the fields that says “Calculation Time:
- xx/xx/xx”. It is in two parts: a TStaticText for the “Calculation Time: ”
- (the label) and another for the time itself. The label is positioned
- kInfoBarMargin pixels from the left. The time field is positioned
- kLabelMargin pixels past the label. }
-
- fCTimeLabelView.GetText(itsText);
- itsStyle := fCTimeLabelView.fTextStyle;
- WITH specCTimeLabel DO BEGIN
- newSize := GetTextWidth(itsText, itsStyle) + kLabelMargin;
- newLocation.h := kInfoBarMargin;
- newLocation.v := fCTimeLabelView.fLocation.v;
- newEnd := newLocation.h + newSize;
- END;
-
- itsText := gMaxWidthTimeString;
- itsStyle := fCTimeView.fTextStyle;
- WITH specCTime DO BEGIN
- newSize := GetTextWidth(itsText, itsStyle) + kTimeSlop;
- newLocation.h := specCTimeLabel.newEnd;
- newLocation.v := fCTimeView.fLocation.v;
- newEnd := newLocation.h + newSize;
- END;
-
- { Do the rightmost item. This is the fields that says “Elapsed Time: xx/xx/xx”.
- It is in two parts: a TStaticText for the “Elapsed Time: ” (the label) and
- another for the time itself.The time field is positioned kInfoBarMargin
- pixels from the right edge of the window. The label is positioned
- kLabelMargin pixels to the right of the time field. }
-
- WITH specETime DO BEGIN
- newSize := specCTime.newSize;
- newEnd := width - kInfoBarMargin;
- newLocation.h := newEnd - newSize;
- newLocation.v := fETimeView.fLocation.v;
- END;
-
- fETimeLabelView.GetText(itsText);
- itsStyle := fETimeLabelView.fTextStyle;
- WITH specETimeLabel DO BEGIN
- newSize := GetTextWidth(itsText, itsStyle) + kLabelMargin;
- newEnd := specETime.newLocation.h;
- newLocation.h := newEnd - newSize;
- newLocation.v := fETimeLabelView.fLocation.v;
- END;
-
- { Center the middle item. This is the TStaticText item that holds the string
- displaying what algorithms we are using. Its position is determined by
- centering it between the left and right edges of the window. }
-
- fMethodNameView.GetText(itsText);
- itsStyle := fMethodNameView.fTextStyle;
- WITH specMethodName DO BEGIN
- newSize := GetTextWidth(itsText, itsStyle);
- newLocation.h := (width - newSize) DIV 2;
- newLocation.v := fMethodNameView.fLocation.v;
- newEnd := newLocation.h + newSize;
- END;
-
- { Make sure we maintain minimum spacing. Examine the locations of the three
- items we’ve just calculated. See if they are all at least kMinimumSpacing
- pixels from each other. If not, then shift everything over to the right to
- maintain this spacing. Note that this means that the Elapsed time field can
- get shoved off the right edge of the window if the window is too small. }
-
- specMethodName.newLocation.h := MAX(specMethodName.newLocation.h, specCTime.newEnd +
- kMinimumSpacing);
- specETimeLabel.newLocation.h := MAX(specETimeLabel.newLocation.h,
- specMethodName.newLocation.h +
- specMethodName.newSize + kMinimumSpacing);
- specETime.newLocation.h := specETimeLabel.newLocation.h + specETimeLabel.newSize;
-
- { Do the percent complete item at the bottom. This guy is centered between the
- left edge of the window, and the right edge of the bottom scrollbar. Note
- that this is the only item here that I move around vertically as well. Moving
- the Percent Complete item also means having to move the little line used to
- seperate it from the fractal view above it. }
-
- bottomScrollbar := fFracAppView.GetScroller(FALSE).fScrollbars[h];
- rightEdge := bottomScrollbar.fLocation.h;
- topEdge := height - bottomScrollbar.fSize.v;
-
- WITH specSingleBar DO BEGIN
- newSize := rightEdge;
- newLocation.h := 0;
- newLocation.v := topEdge + 1;
- END;
-
- fPercentLabelView.GetText(itsText);
- itsStyle := fPercentLabelView.fTextStyle;
- specPercentLabel.newSize := GetTextWidth(itsText, itsStyle);
-
- itsText := kMaxPctString;
- itsStyle := fPercentView.fTextStyle;
- WITH specPercent DO BEGIN
- newSize := GetTextWidth(itsText, itsStyle);
- newLocation.h := (rightEdge - newSize - specPercentLabel.newSize) DIV 2;
- newLocation.v := topEdge + 2;
- newEnd := newLocation.h + newSize;
- END;
-
- WITH specPercentLabel DO BEGIN
- newLocation.h := specPercent.newEnd;
- newLocation.v := topEdge + 2;
- END;
-
- {--- Move them all into position ---}
-
- MoveView(fCTimeLabelView, specCTimeLabel);
- MoveView(fCTimeView, specCTime);
- MoveView(fETimeLabelView, specETimeLabel);
- MoveView(fETimeView, specETime);
- MoveView(fMethodNameView, specMethodName);
- MoveView(fPercentLabelView, specPercentLabel);
- MoveView(fPercentView, specPercent);
- MoveView(fSingleBarView, specSingleBar);
-
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- FUNCTION TFracAppWindow.GetCTimeView: TStaticText;
-
- BEGIN
- GetCTimeView := fCTimeView;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- FUNCTION TFracAppWindow.GetETimeView: TStaticText;
-
- BEGIN
- GetETimeView := fETimeView;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- FUNCTION TFracAppWindow.GetFracAppView: TFracAppView;
-
- BEGIN
- GetFracAppView := fFracAppView;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- FUNCTION TFracAppWindow.GetMethodView: TStaticText;
-
- BEGIN
- GetMethodView := fMethodNameView;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- FUNCTION TFracAppWindow.GetPercentView: TStaticText;
-
- BEGIN
- GetPercentView := fPercentView;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S AOpen}
-
- PROCEDURE TFracAppWindow.Open; OVERRIDE;
-
- { Called by MacApp when it needs to open a window. We override it here so that we can also
- call AdjustInfoBar. }
-
- BEGIN
- SELF.AdjustInfoBar(fSize.h, fSize.v, kDontInvalidate);
- INHERITED Open;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ANonRes}
-
- PROCEDURE TFracAppWindow.Resize(width, height: VCoordinate; invalidate: Boolean); OVERRIDE;
-
- { Called by MacApp when it needs to resize a window. We override it here so that we can also
- call AdjustInfoBar. }
-
- BEGIN
- INHERITED Resize(width, height, invalidate);
- SELF.AdjustInfoBar(width, height, invalidate);
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ANonRes}
-
- PROCEDURE TFracAppWindow.Show(state, redraw: Boolean); OVERRIDE;
-
- { We support a Windows menu by having smarter than average windows. These windows tell
- MacApp when they are showing and hiding themselves. They do this by calling
- TFracAppApplication.InstallWindowMenuItem. That method notes what is going on, and
- rebuilds the Windows menu appropriately. }
-
- BEGIN
- INHERITED Show(state, redraw);
- TFracAppApplication(gApplication).InstallWindowMenuItem(SELF, state);
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppWindow.SetTitle(newTitle: Str255); OVERRIDE;
-
- { If our window changes its name, we need to tell the application so that it can rebuild the
- Windows menu. Otherwise, we’ll have a menu item that refers to a window whose title no
- longer matches that of the menu item. }
-
- BEGIN
- INHERITED SetTitle(newTitle);
- InvalidateMenus;
- gRebuildWindowsMenu := TRUE;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S AFields}
-
- PROCEDURE TFracAppWindow.Fields(PROCEDURE DoToField(fieldName: Str255; fieldAddr: Ptr;
- fieldType: Integer)); OVERRIDE;
-
- BEGIN
- DoToField('TFracAppWindow', NIL, bClass);
- DoToField('fCTimeLabelView', @fCTimeLabelView, bObject);
- DoToField('fCTimeView', @fCTimeView, bObject);
- DoToField('fETimeLabelView', @fETimeLabelView, bObject);
- DoToField('fETimeView', @fETimeView, bObject);
- DoToField('fFracAppView', @fFracAppView, bObject);
- DoToField('fMethodNameView', @fMethodNameView, bObject);
- DoToField('fPercentLabelView', @fPercentLabelView, bObject);
- DoToField('fPercentView', @fPercentView, bObject);
- DoToField('fSingleBarView', @fSingleBarView, bObject);
-
- { Print fields of anscestors }
- INHERITED Fields(DoToField);
- END;
-
- {-------------------------------------------------------------------------------------------}
- {-------------------------------TNoFlashStaticText Methods----------------------------------}
- {-------------------------------------------------------------------------------------------}
- { This object is used to display the elapsed time items, and the percentage complete item. I
- found that with a normal TStaticText item, I got too much flashing when I updated the
- strings. There was too much of a time gap between when the old string got erased, and the
- new one got drawn. I’ve tried to minimize that by having MATextbox (which is what
- ultimately draws the string) erase the old text when it draws the new. There is still SOME
- flicker, but not as much as there used to be. }
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TNoFlashStaticText.ImageText(text: Ptr; LENGTH: LONGINT; box: Rect;
- just: Integer); OVERRIDE;
-
- { Called by TStaticText in its Draw method to do the actual imaging of text. Overridden so
- that it passes the kEraseText parameter to MATextbox. }
-
- BEGIN
- MATextBox(text, LENGTH, box, just, fAutoWrap, NIL, kEraseFirst, kSpaceForCaret);
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TNoFlashStaticText.SetText(theText: Str255; redraw: Boolean); OVERRIDE;
-
- { This method is called to set the text that the TStaticText item displays. It draws the
- text immediately, without invalidating the screen and simply responding to the update
- event. In doing this, it calls EraseRect, and then calls Draw. Overridden to inhibit
- erasing the old text before drawing the new, as we now do that in ImageText. }
-
- VAR
- area: Rect;
-
- BEGIN
- IF (fDataHandle = NIL) | (theText <> fDataHandle^^) THEN BEGIN
- SELF.ReleaseText;
- fDataHandle := NewString(theText);
- IF MemError <> noErr THEN
- fDataHandle := NIL;
- IF redraw & SELF.Focus & SELF.IsVisible THEN BEGIN
- SELF.ControlArea(area);
- { EraseRect(area); } { Removed from original code. }
- SELF.Draw(area);
- END;
- END;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S AFields}
-
- PROCEDURE TNoFlashStaticText.Fields(PROCEDURE DoToField(fieldName: Str255; fieldAddr: Ptr;
- fieldType: Integer)); OVERRIDE;
-
- BEGIN
- DoToField('TNoFlashStaticText', NIL, bClass);
-
- { Print fields of anscestors }
- INHERITED Fields(DoToField);
- END;
-
- {-------------------------------------------------------------------------------------------}
- {----------------------------------TFracAppView Methods-------------------------------------}
- {-------------------------------------------------------------------------------------------}
- { This is the object that displays the fractal. Besides having its .Draw method overridden
- so that it can copybits the offscreen image, this View also keeps track of the current
- selection rectangle. Because of this, it also handles the menu items that would only be
- enabled when there is a selection. This includes Copy, New From Selection, and New
- Multi-Page. }
- {-------------------------------------------------------------------------------------------}
- {$S AOpen}
-
- PROCEDURE TFracAppView.IRes(itsDocument: TDocument; itsSuperView: TView;
- VAR itsParams: Ptr); OVERRIDE;
-
- { Init our FracAppView. This sets the current selection to an empty rect, and keeps a
- reference to our owning document. This is so we can call some methods in that document
- that return information about the picture we have to display. This reference is kept in
- addition to the fDocument field that TViews already have. Our own reference is stored as
- a TFracAppDocument, and not just a plain TDocument. This is because we need a reference to
- a TFracAppDocument in order to access the methods that it has that a normal TDocument
- doesn’t. We could just coerce fDocument into a TFracAppDocument whenever we accessed it,
- but that’s a pain, and this is inexpensive. }
-
- BEGIN
- fSelectionRect := gZeroRect;
- fFracAppDocument := TFracAppDocument(itsDocument);
-
- INHERITED IRes(itsDocument, itsSuperView, itsParams);
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppView.DoHighlightSelection(fromHL, toHL: HLState); OVERRIDE;
-
- { Called by MacApp’s updating routines to highlight the current selection rectangle if there
- is one. This is drawn in srcCopy mode to make it stand out better when it is a final
- selection. XOR is used for the rubberband, until mouseUp. }
-
- VAR
- selPatHandle: PatHandle;
- tempRect: Rect;
-
- BEGIN
- IF SELF.HasSelection THEN BEGIN
- tempRect := SELF.GetSelection;
- IF (toHL = hlOn) THEN BEGIN
-
- selPatHandle := GetPattern(kSelPattern); { get the pattern we use. }
- IF selPatHandle <> NIL THEN { If pattern available, use it. }
- PenPat(selPatHandle^^); { set pen pattern to our selection kind.
- }
- PenMode(srcCopy); { copy mode on pattern selection. }
- FrameRect(tempRect); { outline the frame of selection. }
- END { highlight turned on. }
- ELSE BEGIN
-
- { Turning off the highlight. We need to remove the traces of the selection.
- To do this, redraw that rectangle. }
-
- SELF.Draw(tempRect); { Redraw it to clear selection. }
- END;
- END;
- END; { TFracAppView.DoHighlightSelection }
-
- {-------------------------------------------------------------------------------------------}
- {$S ADoCommand}
-
- FUNCTION TFracAppView.DoMenuCommand(aCmdNumber: CmdNumber): TCommand; OVERRIDE;
-
- { Called by MacApp when a menu item has been chosen. Handle the menu choices for “New From
- Selection” and “New MultiPage…” out of the File Menu. These make new Fractals based on the
- current selection. It does it by calling on the application object to make a new document.
- The communication to the DoInitialState is through the global variable gPageRecord. }
-
- VAR
- pageDialog: TWindow; { input on multiple pages. }
- dismisser: IDType; { Button pressed in multiPage Dialog }
- horPages: LONGINT; { Horizontal number of pages to make }
- verPages: LONGINT; { Vertical number of pages to make }
- qdExtent: Rect; { For Select All }
-
- BEGIN
-
- { Assume that we have no command to return, since none of our commands currently
- change the document. }
-
- DoMenuCommand := gNoChanges;
-
- CASE aCmdNumber OF
-
- cCopy: BEGIN
- FailOSErr(ZeroScrap);
- SELF.WriteToDeskScrap;
- gApplication.CheckDeskScrap; { Force MacApp to notice the change }
- END;
-
- cSelectAll: BEGIN
- SELF.GetQDExtent(qdExtent);
- SELF.SetSelection(qdExtent, kRedraw)
- END;
-
- cNewFromSelection: BEGIN
- fFracAppDocument.StashSelectedRange;
- gApplication.OpenNew(cNewFromSelection);
- END;
-
- cNewMultiPage: BEGIN
-
- { When they choose the MultiPage option, we have to put up the dialog to find
- out what they want to do, get the values back, store them into the
- gPageRecord global, and start up the first document based on the current
- selection and the number of pages to do. }
-
- { Run the dialog to get the number of pages desired. }
-
- pageDialog := TWindow(NewTemplateWindow(kMultiDialog, NIL));
- dismisser := TDialogView(pageDialog.FindSubView('DLOG')).PoseModally;
- horPages := TNumberText(pageDialog.FindSubView('HORZ')).GetValue;
- verPages := TNumberText(pageDialog.FindSubView('VERT')).GetValue;
- pageDialog.Close;
-
- IF dismisser = 'OKOK' THEN BEGIN
-
- fFracAppDocument.StashSelectedRange;
-
- { Now that we have a extended rectangle defining the area to calculate, we
- need to get the next document’s extended rectangle by dividing that large
- rectangle by the number of pages desired. }
-
- WITH gPageRecord DO BEGIN
- RealMax := RealMin + (RealMax - RealMin) / verPages;
- ImagMax := ImagMin + (ImagMax - ImagMin) / horPages;
- END;
-
- { Now we have the page count desired, save it off in the global gPageRecord. }
-
- WITH gPageRecord DO BEGIN
- maxH := horPages;
- maxV := verPages;
- currentH := 1; { Start at page 1 on both axes. }
- currentV := 1;
- END;
-
- { Now we have the actual area to use as the rectangle to calculate, we need to
- turn the real numbers into the page rectangle that we will use to print and
- save and so on. This is done by creating the full document in DoMakeDocument,
- and the global variables set here will be used there. }
-
- gApplication.OpenNew(cNewMultiPage);
- END; { IF dismisser }
- END;
-
- OTHERWISE DoMenuCommand := INHERITED DoMenuCommand(aCmdNumber); { next guy in
- chain. }
- END; { CASE on aCmdNumber }
-
- END; { TFracAppView.DoMenuCommand }
-
- {-------------------------------------------------------------------------------------------}
- {$S ASelCommand}
-
- FUNCTION TFracAppView.DoMouseCommand(VAR theMouse: Point; VAR info: EventInfo;
- VAR hysteresis: Point): TCommand; OVERRIDE;
-
- { Called by MacApp to handle the mouse events in the view. This will pass back the command
- object to handle tracking the mouse and creating a new selection in preparation for making
- a new fractal. }
-
- VAR
- tracker: TAreaSelector;
-
- BEGIN
- New(tracker); { make a new command object. }
- FailNil(tracker); { no memory, trash out. }
- tracker.IAreaSelector(SELF, fFracAppDocument); { Initialize the command object. }
- DoMouseCommand := tracker; { return it for later use. }
- END; { TFracAppView.DoMouseCommand }
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppView.DoSetupMenus; OVERRIDE;
-
- { Called by MacApp when the user clicks on the menu. Set up the New Fractal menus choice in
- Fractal Menu, based on selection. }
-
- BEGIN
- INHERITED DoSetupMenus; { Do mainline stuff first. }
-
- { If we have a non-zero selection, we can enable the menu item to use it as the
- new fractal dimensions for this document. }
-
- Enable(cCopy, SELF.HasSelection);
- Enable(cSelectAll, TRUE);
- Enable(cNewFromSelection, SELF.HasSelection);
- Enable(cNewMultiPage, SELF.HasSelection);
- END; { TFracAppView.DoSetupMenus }
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppView.Draw(area: Rect); OVERRIDE;
-
- { Called by MacApp to draw the view seen in the window. Every nonblank view MUST override
- this method. This is the display routine to take the data out of the offscreen buffer and
- whip it up to the window, as the current view. The fractal is full page size, clips
- without scaling into the window. }
-
- VAR
- destPort: GrafPtr;
- theDevice: GDHandle;
-
- BEGIN
-
- { Lock down our bits so that they don’t move when we try to copy them to the
- screen. }
-
- fFracAppDocument.LockThePixels(kLock);
-
- { Set the fore and background colors. If we just leave them set to any old
- values, Copybits will attempt to “colorize” the bitmap that we transfer. See
- technote #163 for the gory details. }
-
- RGBForeColor(gRGBBlack);
- RGBBackColor(gRGBWhite);
-
- { Copy the bits to the screen, allowing CopyBits to sort out the colors. }
-
- CopyBits(GrafPtr(fFracAppDocument.GetFracPort)^.portBits, thePort^.portBits, area,
- area, srcCopy, NIL);
-
- { We’re done with the pixels, so let ‘em float. }
-
- fFracAppDocument.LockThePixels(kUnlock);
- END; { TFracAppView.Draw }
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppView.GetQDExtent(VAR qdExtent: Rect); OVERRIDE;
-
- { Overridden so that we can redefine the extent of the view when pasting to the scrap. One
- of the things that GetQDExtent is used for is by TView.WriteToDeskScrap. When this method
- is called, it is so that it will image the view into a PICT, and then put this PICT on the
- desk scrap. GetQDExtent is called to find out the boundaries of this PICT. We special case
- GetQDExtent here for when we are drawing to that PICT. We know this by the setting of the
- global variable, gDrawingPICTScrap. In that case, we only want to draw the current
- selection to the scrap. So we just return the fSelectionRect. }
-
- BEGIN
- IF NOT gDrawingPICTScrap THEN
- INHERITED GetQDExtent(qdExtent)
- ELSE
- qdExtent := SELF.GetSelection;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- FUNCTION TFracAppView.GetSelection: Rect;
-
- { Returns the current selection rectangle. Among other routines, this is called by
- TFracAppDocument.StashSelectedRange to get the information it needs to to help create a
- new document based on the current selection. }
-
- BEGIN
- GetSelection := fSelectionRect;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- FUNCTION TFracAppView.HasSelection: Boolean;
-
- { Returns TRUE if there is a selection on the screen. Handy when we are enabling menu items
- based on whether or not there is a selection. }
-
- BEGIN
- {$Push} {$H-}
- HasSelection := NOT EmptyRect(fSelectionRect);
- {$Pop}
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppView.InvalidRect(r: Rect); OVERRIDE;
-
- { InvalidRect is a TView method that invalidates the specified part of the view. We override
- it here to add a little optimization. The ToolBox routine InvalRect does not optimize for
- when the passed rectangle is empty. Neither does MacApp. So we do it here. }
-
- BEGIN
- IF SELF.Focus THEN BEGIN
- VisibleRect(r);
- IF NOT EmptyRect(r) THEN
- InvalRect(r);
- END;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppView.SetSelection(theSelectionRect: Rect; redraw: Boolean);
-
- { Sets and redraws the selection. Redraws the selection only if the redraw parameter is
- TRUE, we can focus (which just means that we have a port to draw into), and if any part of
- our view is visible. If any of those assertions are FALSE, then we just remember the
- selection in fSelectionRect. }
-
- BEGIN
- IF redraw & SELF.Focus & SELF.IsVisible THEN BEGIN
- SELF.DoHighlightSelection(hlOn, hlOff);
- fSelectionRect := theSelectionRect;
- SELF.DoHighlightSelection(hlOff, hlOn);
- END
- ELSE
- fSelectionRect := theSelectionRect;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S AFields}
-
- PROCEDURE TFracAppView.Fields(PROCEDURE DoToField(fieldName: Str255; fieldAddr: Ptr;
- fieldType: Integer)); OVERRIDE;
-
- BEGIN
- DoToField('TFracAppView', NIL, bClass);
- DoToField('fFracAppDocument', @fFracAppDocument, bObject);
- DoToField('fSelectionRect', @fSelectionRect, bRect);
-
- { Print fields of anscestors }
- INHERITED Fields(DoToField);
- END;
-
- {-------------------------------------------------------------------------------------------}
- {---------------------------------TFracAppEngine Methods------------------------------------}
- {-------------------------------------------------------------------------------------------}
- {$S AOpen}
-
- PROCEDURE TFracAppEngine.IFracAppEngine(itsDocument: TFracAppDocument);
-
- { Inits the engine object itself. Remembers a reference to its document, and makes a working
- copy of its FracHeader. This is done for speed so that we don’t have to continually query
- the document for its info. }
-
- BEGIN
- SELF.IObject;
-
- fFracAppDocument := itsDocument;
- fFracHeaderCopy := fFracAppDocument.GetFracHeader;
- END; { TFracAppEngine.IFracAppEngine }
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- FUNCTION TFracAppEngine.CalcCity: Boolean;
-
- { Abstract method for TFracAppEngine. Called by TFracAppDocument.CalcTown. It is meant to be
- overridden. It is only here only so that we can refer to a TNormalFracAppEngine or
- TFastFracAppEngine as a TFracAppEngine, and still call its CalcCity method. Object Pascal
- makes sure that the right one gets called. }
-
- BEGIN
- IF qDebug THEN BEGIN
- ProgramBreak('TFracAppDocument.CalcCity: Override me');
- CalcCity := TRUE; { Signal that we’re done, so that we don’t get called again. }
- END;
- END; { TFracAppEngine.CalcCity }
-
- {-------------------------------------------------------------------------------------------}
- {$S AReadFile}
-
- PROCEDURE TFracAppEngine.DoRead(aRefNum: Integer; rsrcExists, forPrinting: Boolean);
-
- { Read any data we may have written to disk. We didn’t (see DoWrite below), so we don’t have
- much to do in order to not read that back in. }
-
- BEGIN
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S AWriteFile}
-
- PROCEDURE TFracAppEngine.DoWrite(aRefNum: Integer; makingCopy: Boolean);
-
- { Write out any information that TFracAppEngines keep laying around. We don’t have any
- data, so we don’t write anything. This method is called by our document when it’s saving
- itself to disk. }
-
- BEGIN
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppEngine.FAGetCPixel(x, y: Integer; VAR itsRGB: RGBColor);
-
- { Normal Color QuickDraw has an annoying habit of hiding and showing the cursor when calling
- GetCPixel -- even for an offscreen pixmap. This bug has been fixed in 32-bit color
- QuickDraw. This method checks to see what system we are running under. If GetCPixel is OK,
- we call it. Otherwise, we do it by hand. }
-
- VAR
- pixBase: Ptr;
- offPort: CGrafPtr;
- mode: SignedByte;
- pixel: SignedByte;
- rowBytes: Longint;
- pixelPtr: Ptr;
-
- BEGIN
- IF gConfiguration.has32BitQD THEN
- GetCPixel(x, y, itsRGB)
- ELSE BEGIN
-
- { Drag. Gotta do it by hand. This means going into the PixMap and getting the
- pixel by hand. We call the document to get the pointer to the pixmap base. We
- then calculate the offset into the pixmap where our pixel lays. This is easy
- as there is a 1 to 1 correspondance between bytes and pixels. Since the
- pixmap data could be off in 32-bit space somwhere, we have to jump over into
- 32-bit mode in order to make sure we can access our bits. We get the pixel,
- and then jump right back into whatever mode we were in before. After we have
- our pixel, we convert it into an RGB value with Index2Color. Because all of
- this takes up more System calls (GetOffPixBase and GetFracPort make system
- calls, in addition to 2 SwapMMUModes and and Index2Color) versus a single
- GetCPixel, doing all of this by hand is slower than letting the system do it. }
-
- pixBase := fFracAppDocument.GetOffPixBase;
- offPort := fFracAppDocument.GetFracPort;
- rowBytes := BAnd(offPort^.portPixMap^^.rowBytes, $00001FFF);
- pixelPtr := Ptr(Longint(pixBase) + y*rowbytes + x);
- mode := true32B;
- SwapMMUMode(mode);
- pixel := pixelPtr^;
- SwapMMUMode(mode);
- Index2Color(BAnd(pixel,$000000FF), itsRGB);
- END;
- END;
-
- {-------------------------------------------------------------------------------------------}
- { This is the heart and soul of this program. Given a point and some other defining
- constants, this routine figures out what color the point should be. Basically, it works
- like this:
-
- Mandelbrot fractals are calculated on the complex coordinate plane. This means that
- complex numbers of the form a + ib are ploted in x,y fashion on a two dimensional grid.
- The value ‘a’ is plotted in the x, or real, direction, and the value ‘b’ is plotted in the
- y, or imaginary, direction.
-
- Given a point a + ib, we square it and add a complex constant C = Po + iQo. We then check
- to see how far the result is away from the first point. If it is farther than some limit
- (called M here), we are done. If the result is within that limit, we apply the formula
- again to that result. We keep this process up until we either go outside the limit “M”, or
- we have performed this process a certain number of times (called the dwell limit). It is
- this number of iterations that determines the color of the pixel. If we exceed our maximum
- number of iterations, we map the color to black.
-
- The actual routine is in Assembly for speed (about 85-90% of program time is spent in this
- one routine!), but the Pascal representation is shown here.
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppEngine.GoFigger(x, y, Po, Qo: Extended; M, K: Integer; VAR kol: Integer);
- EXTERNAL;
-
- {$IFC FALSE}
- {[f-]}
-
- VAR
- x1, y1 : Extended;
-
- BEGIN
- kol := 0;
- REPEAT
-
- {
- Given a point: pt = x + iy,
- and a constant: C = Po + iQo,
- iteratively calculate pt2 = pt^2 + C
- = (x + iy)^2 + (Po + iQo)
- = (x^2 + 2ixy - y^2) + (Po + iQo)
- = (x^2 - y^2 + Po) + i(2xy + Qo)
- until pt2 is at least “M” units from the original point.
- The number of times that it took us to get to this state gets
- mapped into the color that we use for that point. If we iterated more
- than a certain maximum, then we cap the color to that maximum.
- }
-
- x1 := x * x - y * y + Po;
- y1 := 2 * x * y + Qo;
- x := x1;
- y := y1;
-
- kol := kol + 1;
-
- UNTIL (kol > K) | ((x * x + y * y) > M);
-
- END;
- {[f+]}
- {$ENDC}
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppEngine.ReportRectCompleted(dirtyRect: Rect);
-
- { Bottleneck used by CalcCity to inform the document that part of the fractal has been
- freshly calculated and needs to be copied to the screen. }
-
- BEGIN
- fFracAppDocument.PostDraw;
- fFracAppDocument.ReportRectCompleted(dirtyRect);
- fFracAppDocument.PreDraw;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFracAppEngine.PixWhap(col, row: Integer; VAR itsRect: Rect; VAR itsRGB: RGBColor);
-
- { Called by the engine’s CalcCity method. This method takes a coordinate, calculates the
- color that it should be by calling GoFigger, and then plots that point. It returns the
- bounding rectangle of the point so that the view displaying it can update itself, and it
- returns the color that was plotted as well. This is so we can make boundary comparisons in
- the TFastFracAppDocument routines. }
-
- CONST
- M = 100; { This decides what ‘infinity’ is. If
- value less than this, then loop. }
- K = kNumColors; { Dwell time, ie, the number of
- iterations we make before giving up.
- This is set to match the ‘clut’ created
- for us. }
- kBlackIndex = 255; { entry in our offscreen color table for
- black. }
-
- VAR
- kol: Integer; { color to plot. }
- Po, Qo: Extended; { Our starting point. }
-
- BEGIN
-
- WITH fFracHeaderCopy DO BEGIN
- Po := pages.RealMin + row * deltaP; { next starting point }
- Qo := pages.ImagMin + col * deltaQ;
- END;
-
- SELF.GoFigger(0, 0, Po, Qo, M, K, kol); { go hammer out a pixel color index }
-
- IF kol > K THEN
- kol := kBlackIndex; { Clip it to black if we’re too high. }
-
- Index2Color(kol, itsRGB); { find the corresponding color }
- RGBForeColor(itsRGB); { and set it. }
-
- { Move to the pixel we calculated for, then draw the pixel in right color. This
- could be done by setting the bytes in pixel map directly, since we own the
- PixMap and the buffer. However, since most of the application’s time is spent
- calculating the pixel color (ie, number of iterations through the loop), the
- overhead of using QuickDraw is very small, and we don’t get much speed
- improvement by bypassing it. }
-
- WITH itsRect DO BEGIN { Do a SetRect by hand - I hate calling the System for this. }
- top := row;
- bottom := row + 1;
- left := col;
- right := col + 1;
- END;
- PaintRect(itsRect); { draw pixel in offscreen buffer }
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S AFields}
-
- PROCEDURE TFracAppEngine.Fields(PROCEDURE DoToField(fieldName: Str255; fieldAddr: Ptr;
- fieldType: Integer)); OVERRIDE;
-
- BEGIN
- DoToField('TFracAppEngine', NIL, bClass);
- DoToField('fFracAppDocument', @fFracAppDocument, bObject);
-
- {$Push} {$H-}
- ShowFracHeader('fFracHeaderCopy', fFracHeaderCopy, DoToField);
- {$Pop}
-
- { Print fields of anscestors }
- INHERITED Fields(DoToField);
- END;
-
- {-------------------------------------------------------------------------------------------}
- {------------------------------TNormalFracAppEngine Methods---------------------------------}
- {-------------------------------------------------------------------------------------------}
- {$S AOpen}
-
- PROCEDURE TNormalFracAppEngine.INormalFracAppEngine(itsDocument: TFracAppDocument);
-
- { Initialize our TNormalFracAppEngine object. This sets the version number in our document’s
- FracHeader, and initializes the pen to (0,0). }
-
- BEGIN
- SELF.IFracAppEngine(itsDocument);
- fFracAppDocument.SetVersion(kNormalVersion); { ensure that we have a version 1
- document }
- fCurrentLocation := gZeroPt;
-
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- FUNCTION TNormalFracAppEngine.CalcCity: Boolean; OVERRIDE;
-
- { The procedure to do the idle time processing for the fractal. It does it one pixel at a
- time to avoid any hit on performance for other applications. This is called in response
- to the DoIdle for the application. }
-
- VAR
- tempRect: Rect; { for updating the screen as we
- calculate. }
- doUpdate: Boolean; { TRUE if we finish a row on this
- calculation. }
- documentIsFinished: Boolean; { TRUE if we finish a row on this
- calculation. }
- anRGB: RGBColor;
-
- BEGIN
-
- { Calculate the fractal as we go. Do next pixel here, based on the state saved
- in fCurrentLocation. When done, those variables are updated to go to the next
- location to do. It sets the pixel in the offscreen port to be whatever we
- calculate it to be. After an entire row has been calculated, the appropriate
- rectangle on the screen is invalidated. This will cause the screen to get
- updated the next time through the event loop. }
-
- SELF.PixWhap(fCurrentLocation.h, fCurrentLocation.v, tempRect, anRGB);
-
- { Now we have changed another point in the document. We need to mark it as
- changed so we can save the document. }
-
- fFracAppDocument.BumpChangeCount;
-
- { Up the counters to the next pixel location to do. }
-
- doUpdate := FALSE; { Assume we don’t need update. }
- documentIsFinished := FALSE; { Assume document is not done. }
- WITH fFracHeaderCopy DO BEGIN
- fCurrentLocation.h := fCurrentLocation.h + 1; { up the column count. }
- IF fCurrentLocation.h >= plotWidth THEN BEGIN { did we run off end of row? }
- doUpdate := TRUE; { done with row, force update. }
- fCurrentLocation.h := 0; { start on the next row. }
- fCurrentLocation.v := fCurrentLocation.v + 1; { and up the counter of next
- row to do. }
- IF fCurrentLocation.v >= plotHeight THEN { Check if we are done, and if so,
- set the flag to stop calculations.
- }
- documentIsFinished := TRUE;
- END; { start at next row. }
- END;
-
- { If we finished a row, update that row to the screen. }
-
- IF doUpdate THEN BEGIN
- WITH fFracHeaderCopy DO BEGIN
- WITH tempRect DO BEGIN { Do a SetRect by hand - I hate calling the System for this. }
- left := calcRect.left;
- top := fCurrentLocation.v - 1;
- right := calcRect.right;
- bottom := fCurrentLocation.v;
- END;
- END;
- SELF.ReportRectCompleted(tempRect);
- END;
-
- CalcCity := documentIsFinished;
- END; { TNormalFracAppDocument.CalcCity }
-
- {-------------------------------------------------------------------------------------------}
- {$S AReadFile}
-
- PROCEDURE TNormalFracAppEngine.DoRead(aRefNum: Integer; rsrcExists,
- forPrinting: Boolean); OVERRIDE;
-
- { Routine to read the data from the data fork of the file into our engine. This consists
- only of fCurrentLocation. This method is called by our TDocument.DoRead. The file mark
- is already set, so we only have to read what’s there. }
-
- VAR
- recsize: LONGINT;
-
- BEGIN
- recsize := SizeOf(fCurrentLocation);
- FailOSErr(FSRead(aRefNum, recsize, @fCurrentLocation));
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S AWriteFile}
-
- PROCEDURE TNormalFracAppEngine.DoWrite(aRefNum: Integer; makingCopy: Boolean); OVERRIDE;
-
- { Write out our state variables to disk: fCurrentLocation. This method is called by
- TFracAppDocument.DoWrite, and it sets up the file mark for us, so we just need to start
- writing to whereever we happen to be. }
-
- VAR
- recsize: LONGINT;
-
- BEGIN
- recsize := SizeOf(fCurrentLocation);
- FailOSErr(FSWrite(aRefNum, recsize, @fCurrentLocation));
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S AFields}
-
- PROCEDURE TNormalFracAppEngine.Fields(PROCEDURE
- DoToField(fieldName: Str255; fieldAddr: Ptr;
- fieldType: Integer)); OVERRIDE;
-
- BEGIN
- DoToField('TNormalFracAppEngine', NIL, bClass);
- DoToField('fCurrentLocation', @fCurrentLocation, bPoint);
-
- { Print fields of anscestors }
- INHERITED Fields(DoToField);
- END;
-
- {-------------------------------------------------------------------------------------------}
- {-------------------------------TFastFracAppEngine Methods----------------------------------}
- {-------------------------------------------------------------------------------------------}
- {$S AOpen}
-
- PROCEDURE TFastFracAppEngine.IFastFracAppEngine(itsDocument: TFracAppDocument);
-
- { Initializer for TFastFracAppEngine. This makes sure that the version number in the
- FracHeader of our document is set to the right value, and it creates a TRectStack to hold
- the subdivided rectangles. After that, it seeds the RectStack with 4 rectangles that cover
- the entire document. }
-
- CONST
- kInitialNumberOfRects = 40;
-
- VAR
- aRectStack: TRectStack;
- tempRect: Rect;
-
- BEGIN
- fRectStack := NIL;
- SELF.IFracAppEngine(itsDocument);
- fFracAppDocument.SetVersion(kFastVersion); { ensure that we have a vers 2 document }
-
- New(aRectStack);
- FailNil(aRectStack);
- aRectStack.IRectStack(kInitialNumberOfRects);
- fRectStack := aRectStack;
-
- tempRect := fFracHeaderCopy.calcRect;
- SELF.DivideAndConquer(tempRect);
-
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S AClose}
-
- PROCEDURE TFastFracAppEngine.Free; OVERRIDE;
-
- BEGIN
- FreeIfObject(fRectStack);
- INHERITED Free;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- FUNCTION TFastFracAppEngine.CalcCity: Boolean; OVERRIDE;
-
- { The procedure to do the idle time processing in the document. For a TFastFracAppDocument,
- we use the Mariani/Silver algorithm. This method assumes that if we are given a bounded
- region and the boundary of that region is all one color, then the interior of that region
- is the same color. We can take advantage of this by recursively quartering our pixmap
- until we start getting rectangles that have solid boundaries. Once we get one, we fill it
- in all at once with a big PaintRect. To take into account that we don’t live in a perfect
- world (well...*I* don’t, anyway), and that things start to degrade once we get down to
- smaller scales, we forego the big PaintRect thing once either side of the rectangle we are
- looking at gets to be 4 pixels or smaller. This should take care of any skinny color
- snakes that sneak their way in just because they are too skinny to show up at 72dpi. }
-
- VAR
- thisRect: Rect;
- itsColor: RGBColor;
- fractalIsDone: Boolean;
-
- PROCEDURE CalcAndGetCPixel(curCol, curRow: Integer; VAR itsRGB: RGBColor);
-
- { Modified version of PixWhap. This routine first sees if we calculated the
- value for the specified pixel already. If so, we just return its color. If
- not, we call PixWhap to calculate and plot it, and then return that color to
- the caller. }
-
- VAR
- itsRect: Rect;
-
- BEGIN
- FAGetCPixel(curCol, curRow, itsRGB);
- IF EqualRGB(itsRGB, gRGBWhite) THEN BEGIN
- SELF.PixWhap(curCol, curRow, itsRect, itsRGB);
- END;
-
- END;
-
- FUNCTION BordersAreTheSame(r: Rect; VAR baseRGB: RGBColor): Boolean;
-
- { Takes the given rect, and proceeds to calculate and compare the border
- colors. If they are all the same, we return TRUE. However, as soon as we
- find a different color, we stop calculating, and return FALSE. }
-
- VAR
- anRGB: RGBColor;
- x, y: Integer;
- mismatch: Boolean;
-
- PROCEDURE ComeAcross(y: Integer);
-
- BEGIN
- x := r.left;
- REPEAT
- x := x + 1;
- CalcAndGetCPixel(x, y, anRGB);
- mismatch := NOT EqualRGB(anRGB, baseRGB);
- UNTIL mismatch OR (x = r.right - 1);
- END;
-
- PROCEDURE ComeDown(x: Integer);
-
- BEGIN
- y := r.top;
- REPEAT
- y := y + 1;
- CalcAndGetCPixel(x, y, anRGB);
- mismatch := NOT EqualRGB(anRGB, baseRGB);
- UNTIL mismatch OR (y = r.bottom - 1);
- END;
-
- BEGIN
- BordersAreTheSame := FALSE;
-
- x := r.left;
- y := r.top;
- CalcAndGetCPixel(x, y, baseRGB);
-
- ComeAcross(r.top);
- IF mismatch THEN
- EXIT(BordersAreTheSame);
-
- ComeAcross(r.bottom - 1);
- IF mismatch THEN
- EXIT(BordersAreTheSame);
-
- ComeDown(r.left);
- IF mismatch THEN
- EXIT(BordersAreTheSame);
-
- ComeDown(r.right - 1);
- IF mismatch THEN
- EXIT(BordersAreTheSame);
-
- BordersAreTheSame := TRUE;
- END;
-
- PROCEDURE FillMeUp(thisRect: Rect);
-
- { Sort of a small version of the TNormalFracAppEngine. This routine gets called
- when our rectangle has gotten small enough. It just loops through and
- calculates the pixels one by one. The rectangle shouldn’t be larger than
- about 20 or 25 pixels, so this doesn’t take very long. }
-
- VAR
- x, y: Integer;
- dummyRect: Rect;
- dummyRGB: RGBColor;
-
- BEGIN
- WITH thisRect DO BEGIN
- FOR x := left TO right - 1 DO BEGIN
- FOR y := top TO bottom - 1 DO BEGIN
- CalcAndGetCPixel(x, y, dummyRGB);
- END; { FOR y }
- END; { FOR x }
- END; { WITH thisRect }
- END; { PROCEDURE FillMeUp }
-
-
- BEGIN { TFrastFracAppEngine.CalcCity }
-
- { Get the next rectangle off of the stack, and start to analyze it. It should
- fall into one of three states. It could be very small (less than 4 pixels on
- a side), in which case we just go ahead and fill it in. It could be that
- color of the entire border is the same, in which case we call PaintRect to
- fill it in. Finally, we have the rectangle whose border colors are not the
- same. In this case, we call DivideAndConquer to divvy it up into four smaller
- rectangles, and push them on the stack for later. }
-
- fRectStack.PopRect(thisRect);
-
- WITH thisRect DO BEGIN
- IF ((right - left) <= kMinCCRectSize) | ((bottom - top) <=
- kMinCCRectSize) THEN BEGIN
- FillMeUp(thisRect);
- SELF.ReportRectCompleted(thisRect);
- END
- ELSE IF BordersAreTheSame(thisRect, itsColor) THEN BEGIN
-
- SELF.ReportRectCompleted(thisRect);
-
- { Do an InsetRect by hand for speed. }
-
- top := top + 1;
- left := left + 1;
- bottom := bottom - 1;
- right := right - 1;
-
- RGBForeColor(itsColor);
- PaintRect(thisRect);
-
- END
- ELSE BEGIN
-
- SELF.DivideAndConquer(thisRect);
-
- END;
- END;
-
- { Now we have changed another part of the document. We need to mark it as
- changed so we can save the document. }
-
- fFracAppDocument.BumpChangeCount;
-
- { See if we have finished with the document. This is done by seeing if there
- are any more rectangles to process on the stack. If not, we tell the
- document that we are all done. We also remove the TRectStack object from
- memory, and set its reference to NIL so we don’t try to free it again later. }
-
- fractalIsDone := fRectStack.IsEmpty;
- CalcCity := fractalIsDone;
- IF fractalIsDone THEN BEGIN
- FreeIfObject(fRectStack);
- fRectStack := NIL;
- END;
-
- END; { TFastFracAppEngine.CalcCity }
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFastFracAppEngine.DivideAndConquer(r: Rect);
-
- { Takes the proferred rectangle, divides it into four parts, and pushes them onto the
- TRectStack. This is done carefully so as to make sure that the rectangles don’t just abut
- each other, but that they actually overlap. This is so we don’t go an calculate the right
- border of one rectangle, and then the left border of the rectangle just to its right. If
- they overlap, we only calculate one line of pixels, which doubles as the border of
- two rectangles. }
-
- VAR
- quarterRect: Rect;
-
- BEGIN
- WITH r DO BEGIN
-
- { Push on the lower right corner }
-
- quarterRect.top := (bottom + top - 1) DIV 2;
- quarterRect.left := (right + left - 1) DIV 2;
- quarterRect.bottom := bottom;
- quarterRect.right := right;
- fRectStack.PushRect(quarterRect);
-
- { Push on the lower left corner }
-
- quarterRect.right := quarterRect.left + 1;
- quarterRect.left := left;
- fRectStack.PushRect(quarterRect);
-
- { Push on the upper right corner }
-
- quarterRect.bottom := quarterRect.top + 1;
- quarterRect.top := top;
- quarterRect.left := (right + left - 1) DIV 2;
- quarterRect.right := right;
- fRectStack.PushRect(quarterRect);
-
- { Push on the upper left corner }
-
- quarterRect.right := quarterRect.left + 1;
- quarterRect.left := left;
- fRectStack.PushRect(quarterRect);
-
- END;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S AReadFile}
-
- PROCEDURE TFastFracAppEngine.DoRead(aRefNum: Integer; rsrcExists,
- forPrinting: Boolean); OVERRIDE;
-
- { Routine to read the data from the data fork of the file into our engine. This consists of
- all the Rects that we had in the TRectStack. First, tell the TRectStack to clear itself
- out. Then, read in a count of the number of rectangles that we wrote out. Next, loop for
- that number of times, reading a rectangle and pushing it onto the stack. This method is
- called by our TDocument.DoRead. The file mark is already set, so we only have to read
- what’s there. }
-
- VAR
- i: Integer;
- recsize: LONGINT;
- numberOfRects: Integer;
- theRect: Rect;
-
- BEGIN
- fRectStack.ClearStack;
- recsize := SizeOf(numberOfRects);
- FailOSErr(FSRead(aRefNum, recsize, @numberOfRects));
- IF numberOfRects > 0 THEN BEGIN
- recsize := SizeOf(theRect);
- FOR i := 1 TO numberOfRects DO BEGIN
- FailOSErr(FSRead(aRefNum, recsize, @theRect));
- fRectStack.PushRect(theRect);
- END;
- END
- ELSE BEGIN
- FreeIfObject(fRectStack);
- fRectStack := NIL;
- END;
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S AWriteFile}
-
- PROCEDURE TFastFracAppEngine.DoWrite(aRefNum: Integer; makingCopy: Boolean); OVERRIDE;
-
- { Write out our state variables to disk. This consists of all the rectangles we may have on
- the TRectStack, in addition to a count of the number of rectangles there actually are (for
- when we need to read them back in). To do this, we call a TRectStack method that performs
- a routine for each item on the stack. The routine that we tell it to perform is one that
- takes the current rectangle, and writes it to disk.. This method is called by
- TFracAppDocument.DoWrite, and it sets up the file mark for us, so we just need to start
- writing to wherever we happen to be. }
-
- VAR
- recsize: LONGINT;
- numberOfRects: Integer;
-
- PROCEDURE WriteRect(theRect: Rect);
-
- BEGIN
- FailOSErr(FSWrite(aRefNum, recsize, @theRect));
- END;
-
- BEGIN
-
- recsize := SizeOf(numberOfRects);
- IF fRectStack = NIL THEN BEGIN
- numberOfRects := 0;
- FailOSErr(FSWrite(aRefNum, recsize, @numberOfRects));
- END
- ELSE BEGIN
- numberOfRects := fRectStack.GetSize;
- FailOSErr(FSWrite(aRefNum, recsize, @numberOfRects));
-
- recsize := SizeOf(Rect);
- fRectStack.EachRect(WriteRect, kIterateForward);
- END;
-
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S ARes}
-
- PROCEDURE TFastFracAppEngine.ReportRectCompleted(dirtyRect: Rect); OVERRIDE;
-
- { Bottleneck used by CalcCity to inform the document that part of the fractal has been
- freshly calculated and needs to be copied to the screen. We override it here to fix up the
- rectangle. Our rectangles are chosen so that there is a little bit of overlap between them
- and the rects next to them. We have to get rid of that overlap so that updating is more
- efficient, and so that our “areaCompleted” running total is accurate. }
-
- BEGIN
- WITH dirtyRect DO BEGIN
- IF top > 0 THEN
- top := top + 1;
- IF left > 0 THEN
- left := left + 1;
- END;
- INHERITED ReportRectCompleted(dirtyRect);
- END;
-
- {-------------------------------------------------------------------------------------------}
- {$S AFields}
-
- PROCEDURE TFastFracAppEngine.Fields(PROCEDURE DoToField(fieldName: Str255; fieldAddr: Ptr;
- fieldType: Integer)); OVERRIDE;
-
- BEGIN
- DoToField('TFastFracAppEngine', NIL, bClass);
- DoToField('fRectStack', @fRectStack, bObject);
-
- { Print fields of anscestors }
- INHERITED Fields(DoToField);
- END;
-